Basemap Pusher: catalog and distribution gateway for existing tiles
A Go service that scans existing basemap tile directories and vector files, catalogues them in SQLite, issues scoped access tokens, and serves tiles and downloads to downstream systems. It deliberately does not generate or process raster data: GIS software produces the tiles, this service catalogues, authorises and distributes them. Capabilities - scans basemap tile directories (manifest-based or raw z/x/y) and vector assets (.geojson, .json, .zip, shapefiles with sidecars) - token auth with per-resource grants, expiry and coarse scopes (read, slice) - resource health inspection flags a moved or deleted tile root before a client hits a 404 - downstream push with exponential-backoff retries and manual re-queue - image slicing jobs driven by the GDAL bundled with QGIS, including VRT mosaicking so successive drone flights accumulate into one seamless layer - a narrow, non-admin slicing API for external callers: submit, poll, idempotent by job code, completion callback - ops console covering health, ledger, tokens, pushes, scans, slicing and service info - native Windows service install/start/stop with crash recovery, auto-start and a rotating log file, so no NSSM or WinSW wrapper is needed Verified with 49 tests plus live runs against real tile data and real GDAL.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
# Demo Scripts
|
||||
|
||||
This folder contains runnable demos for the internal remote-sensing result service.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- PostgreSQL and PostGIS are available to the API through `config/service.env`.
|
||||
- The Python environment is available at `PYTHON_EXE`.
|
||||
- Go is installed and can run `go run`.
|
||||
|
||||
## Service Startup
|
||||
|
||||
You can start the stack manually from the repository root:
|
||||
|
||||
```powershell
|
||||
.\start-all.bat
|
||||
```
|
||||
|
||||
Or use the self-contained full-flow demo to start a temporary stack on demo ports:
|
||||
|
||||
```powershell
|
||||
.\demo\demo_full_flow.ps1 -StartStack -StartCallbackReceiver
|
||||
```
|
||||
|
||||
## Demo List
|
||||
|
||||
- `demo_import_and_slice.ps1`
|
||||
Imports a raster result, binds a style, and triggers COG, preview, legend, and tile generation.
|
||||
- `demo_register_target_system.ps1`
|
||||
Creates or updates a callback-based target system.
|
||||
- `demo_push_result.ps1`
|
||||
Pushes an existing managed result to a target system or callback URL.
|
||||
- `demo_geojson_flow.ps1`
|
||||
Registers a GeoJSON resource and optionally pushes it through the same delivery interface.
|
||||
- `demo_full_flow.ps1`
|
||||
Runs the main end-to-end flow: sample raster, import, slice, target registration, and callback push.
|
||||
- `demo_callback_receiver.py`
|
||||
Small local receiver for inspecting push payloads.
|
||||
|
||||
## Suggested Order
|
||||
|
||||
1. Start the services manually, or run `demo_full_flow.ps1 -StartStack -StartCallbackReceiver`.
|
||||
2. Run `demo_import_and_slice.ps1` to create a managed result.
|
||||
3. Run `demo_register_target_system.ps1` to register a callback receiver.
|
||||
4. Run `demo_push_result.ps1` to publish the managed result.
|
||||
5. Run `demo_geojson_flow.ps1 -Push` if you want to verify the retained GeoJSON delivery path.
|
||||
|
||||
## Example Commands
|
||||
|
||||
```powershell
|
||||
.\demo\demo_import_and_slice.ps1
|
||||
.\demo\demo_register_target_system.ps1 -CallbackUrl http://127.0.0.1:8920/callback
|
||||
.\demo\demo_push_result.ps1 -ResultCode demo_result_20260406_120000 -TargetSystemCode demo_callback
|
||||
.\demo\demo_geojson_flow.ps1 -Push -CallbackUrl http://127.0.0.1:8920/callback
|
||||
.\demo\demo_full_flow.ps1 -StartStack -StartCallbackReceiver
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Each demo prints a JSON summary that includes the main identifiers and URLs. Temporary run data is written under `temp/demo-runs/`.
|
||||
|
||||
`source_system_code` is intentionally left empty in the demos. If you want to populate it, seed the `source_systems` table first.
|
||||
+574
@@ -0,0 +1,574 @@
|
||||
param(
|
||||
[string]$EnvFile = (Join-Path $PSScriptRoot "..\config\service.env")
|
||||
)
|
||||
|
||||
$script:RsDemoRepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
$script:RsDemoProcesses = @()
|
||||
$script:RsDemoEnvLoaded = $false
|
||||
|
||||
function Get-RsRepoRoot {
|
||||
return $script:RsDemoRepoRoot
|
||||
}
|
||||
|
||||
function Import-RsDemoEnvironment {
|
||||
param([string]$Path = $EnvFile)
|
||||
|
||||
if ($script:RsDemoEnvLoaded) {
|
||||
return
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath $Path) {
|
||||
foreach ($rawLine in (Get-Content -LiteralPath $Path)) {
|
||||
$line = $rawLine.Trim()
|
||||
if (-not $line -or $line.StartsWith("#")) {
|
||||
continue
|
||||
}
|
||||
if ($line.StartsWith("export ")) {
|
||||
$line = $line.Substring(7).Trim()
|
||||
}
|
||||
$parts = $line.Split("=", 2)
|
||||
if ($parts.Count -ne 2) {
|
||||
continue
|
||||
}
|
||||
$key = $parts[0].Trim()
|
||||
$value = $parts[1].Trim().Trim("`"").Trim("'")
|
||||
if (-not $key) {
|
||||
continue
|
||||
}
|
||||
if (-not (Test-Path "Env:$key")) {
|
||||
Set-Item -Path "Env:$key" -Value $value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $env:PYTHON_EXE) {
|
||||
$env:PYTHON_EXE = "C:\Users\Administrator\.conda\envs\InSAR\python.exe"
|
||||
}
|
||||
if (-not $env:API_BASE_URL) {
|
||||
$env:API_BASE_URL = "http://127.0.0.1:8910"
|
||||
}
|
||||
if (-not $env:TILE_STATIC_BASE_URL) {
|
||||
$env:TILE_STATIC_BASE_URL = "http://127.0.0.1:8900"
|
||||
}
|
||||
if (-not $env:RS_API_BASE_URL) {
|
||||
$env:RS_API_BASE_URL = $env:API_BASE_URL
|
||||
}
|
||||
if (-not $env:PYTHONIOENCODING) {
|
||||
$env:PYTHONIOENCODING = "utf-8"
|
||||
}
|
||||
if (-not $env:NO_PAUSE) {
|
||||
$env:NO_PAUSE = "1"
|
||||
}
|
||||
|
||||
$script:RsDemoEnvLoaded = $true
|
||||
}
|
||||
|
||||
function Get-RsApiBaseUrl {
|
||||
Import-RsDemoEnvironment
|
||||
return ([string]$env:API_BASE_URL).TrimEnd("/")
|
||||
}
|
||||
|
||||
function Get-RsTileBaseUrl {
|
||||
Import-RsDemoEnvironment
|
||||
return ([string]$env:TILE_STATIC_BASE_URL).TrimEnd("/")
|
||||
}
|
||||
|
||||
function Get-RsPythonExe {
|
||||
Import-RsDemoEnvironment
|
||||
if (-not (Test-Path -LiteralPath $env:PYTHON_EXE)) {
|
||||
throw "Python interpreter not found: $($env:PYTHON_EXE)"
|
||||
}
|
||||
return $env:PYTHON_EXE
|
||||
}
|
||||
|
||||
function Get-RsGoCommand {
|
||||
return (Get-Command go -ErrorAction Stop).Source
|
||||
}
|
||||
|
||||
function New-RsRunContext {
|
||||
param([string]$Prefix = "demo")
|
||||
|
||||
$runId = "{0}_{1}" -f $Prefix, (Get-Date -Format "yyyyMMdd_HHmmss")
|
||||
$runDir = Join-Path (Get-RsRepoRoot) ("temp\demo-runs\" + $runId)
|
||||
$logDir = Join-Path $runDir "logs"
|
||||
$outputDir = Join-Path $runDir "output"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $runDir | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $logDir | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
|
||||
|
||||
return [pscustomobject]@{
|
||||
RunID = $runId
|
||||
RunDirectory = $runDir
|
||||
LogDirectory = $logDir
|
||||
OutputDirectory = $outputDir
|
||||
}
|
||||
}
|
||||
|
||||
function ConvertTo-RsFileUri {
|
||||
param([Parameter(Mandatory)] [string]$Path)
|
||||
|
||||
$fullPath = [System.IO.Path]::GetFullPath($Path)
|
||||
return ([System.Uri]$fullPath).AbsoluteUri
|
||||
}
|
||||
|
||||
function Encode-RsURIComponent {
|
||||
param([Parameter(Mandatory)] [string]$Value)
|
||||
|
||||
return [System.Uri]::EscapeDataString($Value)
|
||||
}
|
||||
|
||||
function Wait-RsUrl {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$Url,
|
||||
[int]$TimeoutSec = 60
|
||||
)
|
||||
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSec)
|
||||
do {
|
||||
try {
|
||||
Invoke-RestMethod -Uri $Url -Method Get -TimeoutSec 5 | Out-Null
|
||||
return
|
||||
} catch {
|
||||
Start-Sleep -Milliseconds 500
|
||||
}
|
||||
} while ((Get-Date) -lt $deadline)
|
||||
|
||||
throw "Timed out waiting for $Url"
|
||||
}
|
||||
|
||||
function Invoke-RsApi {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$Method,
|
||||
[Parameter(Mandatory)] [string]$Path,
|
||||
[object]$Body,
|
||||
[int]$TimeoutSec = 30
|
||||
)
|
||||
|
||||
Import-RsDemoEnvironment
|
||||
|
||||
$uri = if ($Path -match "^https?://") {
|
||||
$Path
|
||||
} else {
|
||||
"{0}{1}" -f (Get-RsApiBaseUrl), $Path
|
||||
}
|
||||
|
||||
$params = @{
|
||||
Uri = $uri
|
||||
Method = $Method
|
||||
TimeoutSec = $TimeoutSec
|
||||
Headers = @{
|
||||
Accept = "application/json"
|
||||
}
|
||||
}
|
||||
|
||||
if ($PSBoundParameters.ContainsKey("Body") -and $null -ne $Body) {
|
||||
$params.ContentType = "application/json"
|
||||
$params.Body = ($Body | ConvertTo-Json -Depth 32)
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Invoke-RestMethod @params
|
||||
} catch {
|
||||
$message = $_.Exception.Message
|
||||
$webResponse = $_.Exception.Response
|
||||
if ($webResponse) {
|
||||
try {
|
||||
$stream = $webResponse.GetResponseStream()
|
||||
if ($stream) {
|
||||
$reader = New-Object System.IO.StreamReader($stream)
|
||||
$rawBody = $reader.ReadToEnd()
|
||||
$reader.Dispose()
|
||||
if ($rawBody) {
|
||||
try {
|
||||
$errorEnvelope = $rawBody | ConvertFrom-Json
|
||||
if ($errorEnvelope.error) {
|
||||
$message = "{0} {1}" -f $errorEnvelope.error.code, $errorEnvelope.error.message
|
||||
} else {
|
||||
$message = $rawBody
|
||||
}
|
||||
} catch {
|
||||
$message = $rawBody
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
throw "API call failed: $Method $uri`n$message"
|
||||
}
|
||||
|
||||
if ($null -eq $response) {
|
||||
return $null
|
||||
}
|
||||
if ($response.error) {
|
||||
throw "API error: $Method $uri => $($response.error.code) $($response.error.message)"
|
||||
}
|
||||
return $response.data
|
||||
}
|
||||
|
||||
function Wait-RsJob {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$JobID,
|
||||
[int]$TimeoutSec = 300,
|
||||
[int]$PollIntervalSec = 2
|
||||
)
|
||||
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSec)
|
||||
do {
|
||||
$job = Invoke-RsApi -Method "GET" -Path ("/api/v1/jobs/{0}" -f $JobID)
|
||||
if ($job.status -eq "succeeded") {
|
||||
return $job
|
||||
}
|
||||
if ($job.status -in @("failed", "cancelled")) {
|
||||
throw "Job $JobID finished with status=$($job.status) error=$($job.last_error_message)"
|
||||
}
|
||||
Start-Sleep -Seconds $PollIntervalSec
|
||||
} while ((Get-Date) -lt $deadline)
|
||||
|
||||
throw "Timed out waiting for job $JobID"
|
||||
}
|
||||
|
||||
function Get-RsStyleByCode {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$ProjectCode,
|
||||
[Parameter(Mandatory)] [string]$Code,
|
||||
[switch]$AllowMissing
|
||||
)
|
||||
|
||||
$project = Encode-RsURIComponent $ProjectCode
|
||||
$keyword = Encode-RsURIComponent $Code
|
||||
$items = @(Invoke-RsApi -Method "GET" -Path "/api/v1/styles?project_code=$project&keyword=$keyword")
|
||||
$match = $items | Where-Object { $_.code -eq $Code } | Select-Object -First 1
|
||||
if ($match) {
|
||||
return $match
|
||||
}
|
||||
if ($AllowMissing) {
|
||||
return $null
|
||||
}
|
||||
throw "Style not found: $Code"
|
||||
}
|
||||
|
||||
function Get-RsTargetSystemByCode {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$ProjectCode,
|
||||
[Parameter(Mandatory)] [string]$Code,
|
||||
[switch]$AllowMissing
|
||||
)
|
||||
|
||||
$project = Encode-RsURIComponent $ProjectCode
|
||||
$items = @(Invoke-RsApi -Method "GET" -Path "/api/v1/target-systems?project_code=$project")
|
||||
$match = $items | Where-Object { $_.code -eq $Code } | Select-Object -First 1
|
||||
if ($match) {
|
||||
return $match
|
||||
}
|
||||
if ($AllowMissing) {
|
||||
return $null
|
||||
}
|
||||
throw "Target system not found: $Code"
|
||||
}
|
||||
|
||||
function Get-RsResultByCode {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$ProjectCode,
|
||||
[Parameter(Mandatory)] [string]$Code,
|
||||
[switch]$AllowMissing
|
||||
)
|
||||
|
||||
$project = Encode-RsURIComponent $ProjectCode
|
||||
$keyword = Encode-RsURIComponent $Code
|
||||
$items = @(Invoke-RsApi -Method "GET" -Path "/api/v1/projects/$project/results?keyword=$keyword")
|
||||
$match = $items | Where-Object { $_.code -eq $Code } | Select-Object -First 1
|
||||
if ($match) {
|
||||
return Invoke-RsApi -Method "GET" -Path ("/api/v1/results/{0}" -f $match.id)
|
||||
}
|
||||
if ($AllowMissing) {
|
||||
return $null
|
||||
}
|
||||
throw "Result not found: $Code"
|
||||
}
|
||||
|
||||
function New-RsDemoStyleSpec {
|
||||
param(
|
||||
[string]$StyleID = "demo_continuous_style",
|
||||
[string]$Name = "Demo Continuous Style",
|
||||
[string]$Scope = "project",
|
||||
[double]$MinValue = -1.0,
|
||||
[double]$MaxValue = 1.0,
|
||||
[int]$TileSize = 256,
|
||||
[string]$TileFormat = "webp",
|
||||
[int]$MinZoom = 0,
|
||||
[int]$MaxZoom = 4
|
||||
)
|
||||
|
||||
return @{
|
||||
spec_version = "1.0"
|
||||
id = $StyleID
|
||||
name = $Name
|
||||
version = 1
|
||||
scope = $Scope
|
||||
render = @{
|
||||
mode = "continuous"
|
||||
band = 1
|
||||
resampling = "bilinear"
|
||||
value_domain = @{
|
||||
min = $MinValue
|
||||
max = $MaxValue
|
||||
}
|
||||
color_map = @{
|
||||
type = "linear"
|
||||
stops = @(
|
||||
@{ value = $MinValue; color = "#8c510a"; label = "low" },
|
||||
@{ value = (($MinValue + $MaxValue) / 2.0); color = "#f6e8c3"; label = "mid" },
|
||||
@{ value = $MaxValue; color = "#01665e"; label = "high" }
|
||||
)
|
||||
null_color = "#00000000"
|
||||
}
|
||||
}
|
||||
legend = @{
|
||||
title = $Name
|
||||
orientation = "horizontal"
|
||||
precision = 2
|
||||
}
|
||||
output = @{
|
||||
tile_format = $TileFormat
|
||||
tile_size = $TileSize
|
||||
min_zoom = $MinZoom
|
||||
max_zoom = $MaxZoom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Ensure-RsStyleVersion {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$ProjectCode,
|
||||
[Parameter(Mandatory)] [string]$StyleCode,
|
||||
[string]$StyleName = "Demo Continuous Style",
|
||||
[hashtable]$StyleSpec,
|
||||
[switch]$CreateNewVersion
|
||||
)
|
||||
|
||||
$style = Get-RsStyleByCode -ProjectCode $ProjectCode -Code $StyleCode -AllowMissing
|
||||
if (-not $style) {
|
||||
$style = Invoke-RsApi -Method "POST" -Path "/api/v1/styles" -Body @{
|
||||
project_code = $ProjectCode
|
||||
scope = "project"
|
||||
code = $StyleCode
|
||||
name = $StyleName
|
||||
created_by = "demo"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $CreateNewVersion -and $style.latest_version_id) {
|
||||
return Invoke-RsApi -Method "GET" -Path ("/api/v1/style-versions/{0}" -f $style.latest_version_id)
|
||||
}
|
||||
|
||||
if (-not $StyleSpec) {
|
||||
$StyleSpec = New-RsDemoStyleSpec -StyleID $StyleCode -Name $StyleName
|
||||
}
|
||||
|
||||
return Invoke-RsApi -Method "POST" -Path ("/api/v1/styles/{0}/versions" -f $style.id) -Body @{
|
||||
style_spec = $StyleSpec
|
||||
}
|
||||
}
|
||||
|
||||
function Ensure-RsTargetSystem {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$ProjectCode,
|
||||
[Parameter(Mandatory)] [string]$Code,
|
||||
[Parameter(Mandatory)] [string]$Name,
|
||||
[Parameter(Mandatory)] [string]$CallbackUrl,
|
||||
[string]$CallbackMethod = "POST",
|
||||
[hashtable]$CallbackHeaders,
|
||||
[int]$TimeoutSeconds = 30,
|
||||
[bool]$Enabled = $true,
|
||||
[string]$CreatedBy = "demo"
|
||||
)
|
||||
|
||||
$body = @{
|
||||
code = $Code
|
||||
name = $Name
|
||||
callback_url = $CallbackUrl
|
||||
callback_method = $CallbackMethod
|
||||
timeout_seconds = $TimeoutSeconds
|
||||
enabled = $Enabled
|
||||
callback_headers = $CallbackHeaders
|
||||
}
|
||||
|
||||
$existing = Get-RsTargetSystemByCode -ProjectCode $ProjectCode -Code $Code -AllowMissing
|
||||
if ($existing) {
|
||||
return Invoke-RsApi -Method "PATCH" -Path ("/api/v1/target-systems/{0}" -f $existing.id) -Body $body
|
||||
}
|
||||
|
||||
$body.project_code = $ProjectCode
|
||||
$body.created_by = $CreatedBy
|
||||
return Invoke-RsApi -Method "POST" -Path "/api/v1/target-systems" -Body $body
|
||||
}
|
||||
|
||||
function Start-RsBackgroundProcess {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$FilePath,
|
||||
[string[]]$ArgumentList,
|
||||
[Parameter(Mandatory)] [string]$WorkingDirectory,
|
||||
[Parameter(Mandatory)] [string]$Name,
|
||||
[Parameter(Mandatory)] [string]$LogDirectory
|
||||
)
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $LogDirectory | Out-Null
|
||||
|
||||
$stdout = Join-Path $LogDirectory ($Name + ".stdout.log")
|
||||
$stderr = Join-Path $LogDirectory ($Name + ".stderr.log")
|
||||
$process = Start-Process -FilePath $FilePath `
|
||||
-ArgumentList $ArgumentList `
|
||||
-WorkingDirectory $WorkingDirectory `
|
||||
-PassThru `
|
||||
-RedirectStandardOutput $stdout `
|
||||
-RedirectStandardError $stderr
|
||||
|
||||
$script:RsDemoProcesses += $process
|
||||
return $process
|
||||
}
|
||||
|
||||
function Stop-RsBackgroundProcesses {
|
||||
param([object[]]$Processes = $script:RsDemoProcesses)
|
||||
|
||||
foreach ($process in $Processes) {
|
||||
if ($process -and -not $process.HasExited) {
|
||||
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Start-RsDemoStack {
|
||||
param(
|
||||
[int]$ApiPort = 28910,
|
||||
[int]$TilePort = 28900,
|
||||
[switch]$IncludeWorker,
|
||||
[string]$RunID = ("stack_" + (Get-Date -Format "yyyyMMdd_HHmmss"))
|
||||
)
|
||||
|
||||
Import-RsDemoEnvironment
|
||||
|
||||
$runDir = Join-Path (Get-RsRepoRoot) ("temp\demo-runs\" + $RunID)
|
||||
$logDir = Join-Path $runDir "logs"
|
||||
New-Item -ItemType Directory -Force -Path $runDir | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $logDir | Out-Null
|
||||
|
||||
$apiBaseUrl = "http://127.0.0.1:$ApiPort"
|
||||
$tileBaseUrl = "http://127.0.0.1:$TilePort"
|
||||
|
||||
$env:API_HOST = "127.0.0.1"
|
||||
$env:API_PORT = "$ApiPort"
|
||||
$env:API_BASE_URL = $apiBaseUrl
|
||||
$env:TILE_HOST = "127.0.0.1"
|
||||
$env:TILE_PORT = "$TilePort"
|
||||
$env:TILE_STATIC_BASE_URL = $tileBaseUrl
|
||||
$env:RS_API_BASE_URL = $apiBaseUrl
|
||||
$env:NO_PAUSE = "1"
|
||||
|
||||
$pythonExe = Get-RsPythonExe
|
||||
$goExe = Get-RsGoCommand
|
||||
|
||||
$tileProcess = Start-RsBackgroundProcess `
|
||||
-FilePath $pythonExe `
|
||||
-ArgumentList @((Join-Path (Get-RsRepoRoot) "server.py")) `
|
||||
-WorkingDirectory (Get-RsRepoRoot) `
|
||||
-Name "tile-server" `
|
||||
-LogDirectory $logDir
|
||||
|
||||
$apiProcess = Start-RsBackgroundProcess `
|
||||
-FilePath $goExe `
|
||||
-ArgumentList @("run", ".\cmd\api") `
|
||||
-WorkingDirectory (Join-Path (Get-RsRepoRoot) "services\api-go") `
|
||||
-Name "api-service" `
|
||||
-LogDirectory $logDir
|
||||
|
||||
Wait-RsUrl -Url "$tileBaseUrl/health" -TimeoutSec 90
|
||||
Wait-RsUrl -Url "$apiBaseUrl/ready" -TimeoutSec 120
|
||||
|
||||
$workerProcess = $null
|
||||
if ($IncludeWorker) {
|
||||
$workerProcess = Start-RsBackgroundProcess `
|
||||
-FilePath $pythonExe `
|
||||
-ArgumentList @((Join-Path (Get-RsRepoRoot) "services\worker-py\main.py")) `
|
||||
-WorkingDirectory (Get-RsRepoRoot) `
|
||||
-Name "python-worker" `
|
||||
-LogDirectory $logDir
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
|
||||
$processes = @($tileProcess, $apiProcess)
|
||||
if ($workerProcess) {
|
||||
$processes += $workerProcess
|
||||
}
|
||||
|
||||
return [pscustomobject]@{
|
||||
RunID = $RunID
|
||||
RunDirectory = $runDir
|
||||
LogDirectory = $logDir
|
||||
ApiBaseUrl = $apiBaseUrl
|
||||
TileBaseUrl = $tileBaseUrl
|
||||
Processes = $processes
|
||||
}
|
||||
}
|
||||
|
||||
function Start-RsCallbackReceiver {
|
||||
param(
|
||||
[int]$Port = 28920,
|
||||
[string]$Path = "/callback",
|
||||
[string]$RunID = ("callback_" + (Get-Date -Format "yyyyMMdd_HHmmss"))
|
||||
)
|
||||
|
||||
$runDir = Join-Path (Get-RsRepoRoot) ("temp\demo-runs\" + $RunID)
|
||||
$logDir = Join-Path $runDir "logs"
|
||||
$outputDir = Join-Path $runDir "callback-records"
|
||||
New-Item -ItemType Directory -Force -Path $logDir | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
|
||||
|
||||
$pythonExe = Get-RsPythonExe
|
||||
$process = Start-RsBackgroundProcess `
|
||||
-FilePath $pythonExe `
|
||||
-ArgumentList @(
|
||||
(Join-Path $PSScriptRoot "demo_callback_receiver.py"),
|
||||
"--host", "127.0.0.1",
|
||||
"--port", "$Port",
|
||||
"--path", $Path,
|
||||
"--output-dir", $outputDir
|
||||
) `
|
||||
-WorkingDirectory (Get-RsRepoRoot) `
|
||||
-Name "callback-receiver" `
|
||||
-LogDirectory $logDir
|
||||
|
||||
$callbackUrl = "http://127.0.0.1:$Port$Path"
|
||||
Wait-RsUrl -Url "http://127.0.0.1:$Port/health" -TimeoutSec 30
|
||||
|
||||
return [pscustomobject]@{
|
||||
RunID = $RunID
|
||||
RunDirectory = $runDir
|
||||
LogDirectory = $logDir
|
||||
OutputDirectory = $outputDir
|
||||
CallbackURL = $callbackUrl
|
||||
Process = $process
|
||||
}
|
||||
}
|
||||
|
||||
function Get-RsLatestCallbackRecord {
|
||||
param([Parameter(Mandatory)] [string]$OutputDirectory)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $OutputDirectory)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$file = Get-ChildItem -LiteralPath $OutputDirectory -Filter "*.json" |
|
||||
Sort-Object LastWriteTimeUtc -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
if (-not $file) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return [pscustomobject]@{
|
||||
Path = $file.FullName
|
||||
Payload = (Get-Content -LiteralPath $file.FullName -Raw | ConvertFrom-Json)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def bootstrap_gdal_env() -> None:
|
||||
env_root = Path(sys.executable).resolve().parent
|
||||
gdal_paths = [
|
||||
env_root / "Lib" / "site-packages" / "rasterio" / "gdal_data",
|
||||
env_root / "Library" / "share" / "gdal",
|
||||
env_root / "share" / "gdal",
|
||||
]
|
||||
proj_paths = [
|
||||
env_root / "Lib" / "site-packages" / "rasterio" / "proj_data",
|
||||
env_root / "Library" / "share" / "proj",
|
||||
env_root / "share" / "proj",
|
||||
]
|
||||
|
||||
for path in gdal_paths:
|
||||
if path.exists():
|
||||
os.environ["GDAL_DATA"] = str(path)
|
||||
break
|
||||
for path in proj_paths:
|
||||
if path.exists():
|
||||
os.environ["PROJ_LIB"] = str(path)
|
||||
os.environ["PROJ_DATA"] = str(path)
|
||||
break
|
||||
|
||||
|
||||
bootstrap_gdal_env()
|
||||
|
||||
import numpy as np
|
||||
import rasterio
|
||||
from rasterio.transform import from_origin
|
||||
|
||||
|
||||
def build_values(width: int, height: int, preset: str) -> np.ndarray:
|
||||
yy, xx = np.mgrid[0:height, 0:width]
|
||||
x = (xx / max(width - 1, 1)).astype("float32")
|
||||
y = (yy / max(height - 1, 1)).astype("float32")
|
||||
|
||||
if preset == "phase":
|
||||
values = np.sin((x * 8.0) + (y * 4.0)) * np.cos(y * 10.0)
|
||||
else:
|
||||
values = (x * 2.0 - 1.0) * 0.7 + np.sin(y * np.pi * 3.0) * 0.3
|
||||
|
||||
return values.astype("float32")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Create a sample GeoTIFF for demos.")
|
||||
parser.add_argument("--output", required=True, help="Output GeoTIFF path.")
|
||||
parser.add_argument("--width", type=int, default=512)
|
||||
parser.add_argument("--height", type=int, default=512)
|
||||
parser.add_argument("--preset", choices=["ndvi", "phase"], default="ndvi")
|
||||
parser.add_argument("--origin-x", type=float, default=110.0)
|
||||
parser.add_argument("--origin-y", type=float, default=22.0)
|
||||
parser.add_argument("--pixel-size", type=float, default=0.01)
|
||||
args = parser.parse_args()
|
||||
|
||||
output = Path(args.output).resolve()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
data = build_values(args.width, args.height, args.preset)
|
||||
|
||||
with rasterio.open(
|
||||
output,
|
||||
"w",
|
||||
driver="GTiff",
|
||||
width=args.width,
|
||||
height=args.height,
|
||||
count=1,
|
||||
dtype="float32",
|
||||
crs="EPSG:4326",
|
||||
transform=from_origin(args.origin_x, args.origin_y, args.pixel_size, args.pixel_size),
|
||||
nodata=-9999.0,
|
||||
) as dst:
|
||||
dst.write(data, 1)
|
||||
|
||||
print(output)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,109 @@
|
||||
import argparse
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Simple callback receiver for tile-server demos.")
|
||||
parser.add_argument("--host", default="127.0.0.1")
|
||||
parser.add_argument("--port", type=int, default=8920)
|
||||
parser.add_argument("--path", default="/callback")
|
||||
parser.add_argument("--output-dir", required=True)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
class State:
|
||||
def __init__(self, output_dir: Path, callback_path: str):
|
||||
self.output_dir = output_dir
|
||||
self.callback_path = callback_path
|
||||
self.counter = 0
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def next_file(self) -> Path:
|
||||
with self.lock:
|
||||
self.counter += 1
|
||||
name = f"{time.strftime('%Y%m%d_%H%M%S')}_{self.counter:04d}.json"
|
||||
return self.output_dir / name
|
||||
|
||||
|
||||
def make_handler(state: State):
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path == "/health":
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"status": "ok"}).encode("utf-8"))
|
||||
return
|
||||
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
def do_POST(self):
|
||||
self._handle_payload()
|
||||
|
||||
def do_PUT(self):
|
||||
self._handle_payload()
|
||||
|
||||
def _handle_payload(self):
|
||||
if self.path != state.callback_path:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
|
||||
length = int(self.headers.get("Content-Length", "0"))
|
||||
raw = self.rfile.read(length)
|
||||
text = raw.decode("utf-8", errors="replace")
|
||||
try:
|
||||
body_json = json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
body_json = None
|
||||
|
||||
record = {
|
||||
"received_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||
"method": self.command,
|
||||
"path": self.path,
|
||||
"headers": {key: value for key, value in self.headers.items()},
|
||||
"body_text": text,
|
||||
"body_json": body_json,
|
||||
}
|
||||
|
||||
output_path = state.next_file()
|
||||
output_path.write_text(json.dumps(record, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(f"[callback] saved {output_path}", flush=True)
|
||||
|
||||
response = {"saved": str(output_path), "ok": True}
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(response).encode("utf-8"))
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
print("[callback] " + (fmt % args), flush=True)
|
||||
|
||||
return Handler
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
output_dir = Path(args.output_dir).resolve()
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
state = State(output_dir=output_dir, callback_path=args.path)
|
||||
server = ThreadingHTTPServer((args.host, args.port), make_handler(state))
|
||||
print(f"Callback receiver listening on http://{args.host}:{args.port}{args.path}", flush=True)
|
||||
print(f"Health endpoint: http://{args.host}:{args.port}/health", flush=True)
|
||||
print(f"Output dir: {output_dir}", flush=True)
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
server.server_close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,213 @@
|
||||
param(
|
||||
[string]$ProjectCode = "default",
|
||||
[string]$ResultCode = ("demo_full_" + (Get-Date -Format "yyyyMMdd_HHmmss")),
|
||||
[string]$ResultType = "ndvi",
|
||||
[string]$SourceSystemCode = "",
|
||||
[string]$RequestedBy = "demo-user",
|
||||
[string]$StyleCode = "demo_continuous_style",
|
||||
[string]$TargetSystemCode = "demo_callback",
|
||||
[switch]$StartStack,
|
||||
[switch]$StartCallbackReceiver,
|
||||
[switch]$KeepProcesses,
|
||||
[int]$ApiPort = 28910,
|
||||
[int]$TilePort = 28900,
|
||||
[int]$CallbackPort = 28920,
|
||||
[int]$ZoomMin = 0,
|
||||
[int]$ZoomMax = 3
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "common.ps1")
|
||||
|
||||
$stack = $null
|
||||
$callback = $null
|
||||
$run = New-RsRunContext -Prefix "full_flow"
|
||||
|
||||
try {
|
||||
Import-RsDemoEnvironment
|
||||
|
||||
if ($StartStack) {
|
||||
$stack = Start-RsDemoStack -ApiPort $ApiPort -TilePort $TilePort -IncludeWorker -RunID $run.RunID
|
||||
}
|
||||
|
||||
$callbackUrl = ""
|
||||
if ($StartCallbackReceiver) {
|
||||
$callback = Start-RsCallbackReceiver -Port $CallbackPort -RunID ($run.RunID + "_callback")
|
||||
$callbackUrl = $callback.CallbackURL
|
||||
}
|
||||
|
||||
$inputPath = Join-Path $run.OutputDirectory ($ResultCode + ".tif")
|
||||
& (Get-RsPythonExe) (Join-Path $PSScriptRoot "create_sample_raster.py") `
|
||||
--output $inputPath `
|
||||
--preset "ndvi" | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to create sample raster"
|
||||
}
|
||||
|
||||
$inspection = Invoke-RsApi -Method "POST" -Path "/api/v1/admin/raster-inspections" -Body @{
|
||||
path = [System.IO.Path]::GetFullPath($inputPath)
|
||||
}
|
||||
|
||||
$styleVersion = Ensure-RsStyleVersion -ProjectCode $ProjectCode -StyleCode $StyleCode -StyleName "Demo Continuous Style"
|
||||
|
||||
$project = Encode-RsURIComponent $ProjectCode
|
||||
$importBody = @{
|
||||
requested_by = $RequestedBy
|
||||
asset = @{
|
||||
source_ref = $inspection.storage_uri
|
||||
asset_type = "raster"
|
||||
display_name = $ResultCode
|
||||
description = "Imported by demo_full_flow.ps1"
|
||||
metadata = @{
|
||||
demo_run = $run.RunID
|
||||
}
|
||||
}
|
||||
asset_version = @{
|
||||
format = $inspection.format
|
||||
storage_uri = $inspection.storage_uri
|
||||
mime_type = $inspection.mime_type
|
||||
size_bytes = $inspection.size_bytes
|
||||
crs = $inspection.crs
|
||||
width = $inspection.width
|
||||
height = $inspection.height
|
||||
band_count = $inspection.band_count
|
||||
dtype = $inspection.dtype
|
||||
nodata_value = $inspection.nodata_value
|
||||
bbox = $inspection.bbox
|
||||
resolution_x = $inspection.resolution_x
|
||||
resolution_y = $inspection.resolution_y
|
||||
metadata = $inspection.metadata
|
||||
}
|
||||
product = @{
|
||||
code = $ResultCode
|
||||
type = $ResultType
|
||||
name = $ResultCode
|
||||
description = "Demo end-to-end result"
|
||||
}
|
||||
product_version = @{
|
||||
value_type = "continuous"
|
||||
unit = "index"
|
||||
bbox = $inspection.bbox
|
||||
status = "ready"
|
||||
}
|
||||
style_binding = @{
|
||||
style_version_id = $styleVersion.id
|
||||
is_default = $true
|
||||
availability = "enabled"
|
||||
}
|
||||
}
|
||||
if ($SourceSystemCode) {
|
||||
$importBody.source_system_code = $SourceSystemCode
|
||||
}
|
||||
|
||||
$import = Invoke-RsApi -Method "POST" -Path "/api/v1/projects/$project/imports/results" -Body $importBody
|
||||
|
||||
$slice = Invoke-RsApi -Method "POST" -Path ("/api/v1/results/{0}/slice" -f $import.product.product.id) -Body @{
|
||||
requested_by = $RequestedBy
|
||||
build_cog = $true
|
||||
generate_preview = $true
|
||||
generate_legend = $true
|
||||
generate_tiles = $true
|
||||
build_cog_input = @{
|
||||
compression = "deflate"
|
||||
block_size = 256
|
||||
force_rebuild = $true
|
||||
}
|
||||
preview_input = @{
|
||||
style_version_id = $styleVersion.id
|
||||
width = 512
|
||||
height = 512
|
||||
format = "png"
|
||||
force_rebuild = $true
|
||||
}
|
||||
legend_input = @{
|
||||
style_version_id = $styleVersion.id
|
||||
width = 320
|
||||
height = 80
|
||||
format = "png"
|
||||
force_rebuild = $true
|
||||
}
|
||||
tiles_input = @{
|
||||
style_version_id = $styleVersion.id
|
||||
zoom_min = $ZoomMin
|
||||
zoom_max = $ZoomMax
|
||||
tile_size = 256
|
||||
tile_format = "webp"
|
||||
force_rebuild = $true
|
||||
}
|
||||
}
|
||||
|
||||
$sliceJobs = @()
|
||||
foreach ($job in @($slice.jobs)) {
|
||||
$sliceJobs += Wait-RsJob -JobID $job.id
|
||||
}
|
||||
|
||||
if ($callbackUrl) {
|
||||
Ensure-RsTargetSystem `
|
||||
-ProjectCode $ProjectCode `
|
||||
-Code $TargetSystemCode `
|
||||
-Name "Demo Callback Receiver" `
|
||||
-CallbackUrl $callbackUrl `
|
||||
-CallbackMethod "POST" `
|
||||
-TimeoutSeconds 30 `
|
||||
-CreatedBy $RequestedBy `
|
||||
-CallbackHeaders @{
|
||||
"X-Demo-Mode" = "full-flow"
|
||||
} | Out-Null
|
||||
}
|
||||
|
||||
$pushBody = @{
|
||||
requested_by = $RequestedBy
|
||||
style_version_id = $styleVersion.id
|
||||
include_artifacts = $true
|
||||
}
|
||||
if ($callbackUrl) {
|
||||
$pushBody.target_system_code = $TargetSystemCode
|
||||
} else {
|
||||
$pushBody.callback_url = "http://127.0.0.1:8920/callback"
|
||||
}
|
||||
|
||||
$push = Invoke-RsApi -Method "POST" -Path ("/api/v1/results/{0}/pushes" -f $import.product.product.id) -Body $pushBody
|
||||
$pushJob = Wait-RsJob -JobID $push.job.id
|
||||
|
||||
$delivery = Invoke-RsApi -Method "GET" -Path ("/api/v1/projects/{0}/products/{1}/delivery?version=latest&style_version_id={2}" -f `
|
||||
(Encode-RsURIComponent $ProjectCode), `
|
||||
(Encode-RsURIComponent $ResultCode), `
|
||||
(Encode-RsURIComponent $styleVersion.id))
|
||||
|
||||
$callbackRecord = $null
|
||||
if ($callback) {
|
||||
Start-Sleep -Seconds 1
|
||||
$callbackRecord = Get-RsLatestCallbackRecord -OutputDirectory $callback.OutputDirectory
|
||||
}
|
||||
|
||||
[ordered]@{
|
||||
run_id = $run.RunID
|
||||
api_base_url = Get-RsApiBaseUrl
|
||||
tile_base_url = Get-RsTileBaseUrl
|
||||
callback_url = if ($callback) { $callback.CallbackURL } else { $push.callback_url }
|
||||
result_id = $import.product.product.id
|
||||
result_code = $ResultCode
|
||||
product_version_id = $import.product_version.id
|
||||
style_version_id = $styleVersion.id
|
||||
slice_job_ids = @($sliceJobs | ForEach-Object { $_.id })
|
||||
push_job_id = $push.job.id
|
||||
push_job_status = $pushJob.status
|
||||
cog_url = $delivery.downloads.cog_url
|
||||
preview_url = if ($delivery.preview) { $delivery.preview.url } else { $null }
|
||||
legend_url = if ($delivery.legend) { $delivery.legend.url } else { $null }
|
||||
tileset_url = if ($delivery.tileset) { $delivery.tileset.url_template } else { $null }
|
||||
callback_record_path = if ($callbackRecord) { $callbackRecord.Path } else { $null }
|
||||
run_directory = $run.RunDirectory
|
||||
log_directory = if ($stack) { $stack.LogDirectory } else { $null }
|
||||
} | ConvertTo-Json -Depth 16
|
||||
}
|
||||
finally {
|
||||
if (-not $KeepProcesses) {
|
||||
if ($callback) {
|
||||
Stop-RsBackgroundProcesses -Processes @($callback.Process)
|
||||
}
|
||||
if ($stack) {
|
||||
Stop-RsBackgroundProcesses -Processes $stack.Processes
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
param(
|
||||
[string]$ProjectCode = "default",
|
||||
[string]$ResourceCode = ("demo_geojson_" + (Get-Date -Format "yyyyMMdd_HHmmss")),
|
||||
[string]$InputPath = "",
|
||||
[string]$RequestedBy = "demo-user",
|
||||
[switch]$Push,
|
||||
[string]$TargetSystemCode = "",
|
||||
[string]$CallbackUrl = "",
|
||||
[bool]$WaitForJob = $true,
|
||||
[switch]$StartStack,
|
||||
[switch]$StartCallbackReceiver,
|
||||
[switch]$KeepProcesses,
|
||||
[int]$ApiPort = 28910,
|
||||
[int]$TilePort = 28900,
|
||||
[int]$CallbackPort = 28920
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "common.ps1")
|
||||
|
||||
$stack = $null
|
||||
$callback = $null
|
||||
$run = New-RsRunContext -Prefix "geojson_flow"
|
||||
|
||||
try {
|
||||
Import-RsDemoEnvironment
|
||||
|
||||
if ($StartStack) {
|
||||
$stack = Start-RsDemoStack -ApiPort $ApiPort -TilePort $TilePort -IncludeWorker -RunID $run.RunID
|
||||
}
|
||||
if ($StartCallbackReceiver) {
|
||||
$callback = Start-RsCallbackReceiver -Port $CallbackPort -RunID ($run.RunID + "_callback")
|
||||
$Push = $true
|
||||
$CallbackUrl = $callback.CallbackURL
|
||||
}
|
||||
|
||||
if (-not $InputPath) {
|
||||
$geojsonDir = Join-Path (Get-RsRepoRoot) "data\geojson\demo"
|
||||
New-Item -ItemType Directory -Force -Path $geojsonDir | Out-Null
|
||||
$InputPath = Join-Path $geojsonDir ($ResourceCode + ".geojson")
|
||||
@"
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"code": "$ResourceCode",
|
||||
"kind": "demo"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[110.0, 20.0],
|
||||
[112.0, 20.0],
|
||||
[112.0, 22.0],
|
||||
[110.0, 22.0],
|
||||
[110.0, 20.0]
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"@ | Set-Content -LiteralPath $InputPath -Encoding UTF8
|
||||
}
|
||||
|
||||
$publicUrl = "/geojson/demo/" + [System.IO.Path]::GetFileName($InputPath)
|
||||
$project = Encode-RsURIComponent $ProjectCode
|
||||
$resource = Invoke-RsApi -Method "POST" -Path "/api/v1/projects/$project/geojson-resources" -Body @{
|
||||
code = $ResourceCode
|
||||
name = $ResourceCode
|
||||
description = "Registered by demo_geojson_flow.ps1"
|
||||
storage_uri = ConvertTo-RsFileUri $InputPath
|
||||
public_url = $publicUrl
|
||||
metadata = @{
|
||||
demo_run = $run.RunID
|
||||
}
|
||||
}
|
||||
|
||||
$pushResponse = $null
|
||||
$job = $null
|
||||
if ($Push -or $TargetSystemCode -or $CallbackUrl) {
|
||||
$body = @{
|
||||
resource_kind = "geojson_resource"
|
||||
geojson_resource_id = $resource.id
|
||||
requested_by = $RequestedBy
|
||||
}
|
||||
if ($TargetSystemCode) {
|
||||
$body.target_system_code = $TargetSystemCode
|
||||
}
|
||||
if ($CallbackUrl) {
|
||||
$body.callback_url = $CallbackUrl
|
||||
}
|
||||
if (-not $TargetSystemCode -and -not $CallbackUrl) {
|
||||
$body.callback_url = "http://127.0.0.1:8920/callback"
|
||||
}
|
||||
|
||||
$pushResponse = Invoke-RsApi -Method "POST" -Path "/api/v1/projects/$project/delivery-pushes" -Body $body
|
||||
if ($WaitForJob) {
|
||||
$job = Wait-RsJob -JobID $pushResponse.job.id
|
||||
}
|
||||
}
|
||||
|
||||
$callbackRecord = $null
|
||||
if ($callback) {
|
||||
Start-Sleep -Seconds 1
|
||||
$callbackRecord = Get-RsLatestCallbackRecord -OutputDirectory $callback.OutputDirectory
|
||||
}
|
||||
|
||||
[ordered]@{
|
||||
run_id = $run.RunID
|
||||
api_base_url = Get-RsApiBaseUrl
|
||||
tile_base_url = Get-RsTileBaseUrl
|
||||
resource_id = $resource.id
|
||||
resource_code = $resource.code
|
||||
input_path = [System.IO.Path]::GetFullPath($InputPath)
|
||||
public_url = $resource.public_url
|
||||
download_url = $resource.download_url
|
||||
push_job_id = if ($pushResponse) { $pushResponse.job.id } else { $null }
|
||||
push_job_status = if ($job) { $job.status } elseif ($pushResponse) { $pushResponse.job.status } else { $null }
|
||||
callback_url = if ($pushResponse) { $pushResponse.callback_url } else { $CallbackUrl }
|
||||
callback_record_path = if ($callbackRecord) { $callbackRecord.Path } else { $null }
|
||||
} | ConvertTo-Json -Depth 12
|
||||
}
|
||||
finally {
|
||||
if (-not $KeepProcesses) {
|
||||
if ($callback) {
|
||||
Stop-RsBackgroundProcesses -Processes @($callback.Process)
|
||||
}
|
||||
if ($stack) {
|
||||
Stop-RsBackgroundProcesses -Processes $stack.Processes
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
param(
|
||||
[string]$ProjectCode = "default",
|
||||
[string]$ResultCode = ("demo_result_" + (Get-Date -Format "yyyyMMdd_HHmmss")),
|
||||
[string]$ResultType = "ndvi",
|
||||
[string]$InputPath = "",
|
||||
[string]$SourceSystemCode = "",
|
||||
[string]$RequestedBy = "demo-user",
|
||||
[string]$StyleCode = "demo_continuous_style",
|
||||
[string]$StyleName = "Demo Continuous Style",
|
||||
[int]$ZoomMin = 0,
|
||||
[int]$ZoomMax = 3,
|
||||
[bool]$WaitForJobs = $true,
|
||||
[switch]$StartStack,
|
||||
[switch]$KeepStack,
|
||||
[int]$ApiPort = 28910,
|
||||
[int]$TilePort = 28900
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "common.ps1")
|
||||
|
||||
$stack = $null
|
||||
$run = New-RsRunContext -Prefix "import_slice"
|
||||
|
||||
try {
|
||||
Import-RsDemoEnvironment
|
||||
|
||||
if ($StartStack) {
|
||||
$stack = Start-RsDemoStack -ApiPort $ApiPort -TilePort $TilePort -IncludeWorker -RunID $run.RunID
|
||||
}
|
||||
|
||||
if (-not $InputPath) {
|
||||
$InputPath = Join-Path $run.OutputDirectory ($ResultCode + ".tif")
|
||||
& (Get-RsPythonExe) (Join-Path $PSScriptRoot "create_sample_raster.py") `
|
||||
--output $InputPath `
|
||||
--preset "ndvi" | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to create sample raster"
|
||||
}
|
||||
}
|
||||
|
||||
$inspection = Invoke-RsApi -Method "POST" -Path "/api/v1/admin/raster-inspections" -Body @{
|
||||
path = [System.IO.Path]::GetFullPath($InputPath)
|
||||
}
|
||||
|
||||
$styleVersion = Ensure-RsStyleVersion -ProjectCode $ProjectCode -StyleCode $StyleCode -StyleName $StyleName
|
||||
|
||||
$assetVersionBody = @{
|
||||
format = $inspection.format
|
||||
storage_uri = $inspection.storage_uri
|
||||
mime_type = $inspection.mime_type
|
||||
size_bytes = $inspection.size_bytes
|
||||
crs = $inspection.crs
|
||||
width = $inspection.width
|
||||
height = $inspection.height
|
||||
band_count = $inspection.band_count
|
||||
dtype = $inspection.dtype
|
||||
bbox = $inspection.bbox
|
||||
metadata = $inspection.metadata
|
||||
}
|
||||
if ($inspection.PSObject.Properties.Name -contains "nodata_value" -and $null -ne $inspection.nodata_value) {
|
||||
$assetVersionBody.nodata_value = $inspection.nodata_value
|
||||
}
|
||||
if ($inspection.PSObject.Properties.Name -contains "resolution_x" -and $null -ne $inspection.resolution_x) {
|
||||
$assetVersionBody.resolution_x = $inspection.resolution_x
|
||||
}
|
||||
if ($inspection.PSObject.Properties.Name -contains "resolution_y" -and $null -ne $inspection.resolution_y) {
|
||||
$assetVersionBody.resolution_y = $inspection.resolution_y
|
||||
}
|
||||
|
||||
$project = Encode-RsURIComponent $ProjectCode
|
||||
$importBody = @{
|
||||
requested_by = $RequestedBy
|
||||
asset = @{
|
||||
source_ref = $inspection.storage_uri
|
||||
asset_type = "raster"
|
||||
display_name = $ResultCode
|
||||
description = "Imported by demo_import_and_slice.ps1"
|
||||
metadata = @{
|
||||
demo_run = $run.RunID
|
||||
}
|
||||
}
|
||||
asset_version = $assetVersionBody
|
||||
product = @{
|
||||
code = $ResultCode
|
||||
type = $ResultType
|
||||
name = $ResultCode
|
||||
description = "Demo managed result"
|
||||
}
|
||||
product_version = @{
|
||||
value_type = "continuous"
|
||||
unit = "index"
|
||||
bbox = $inspection.bbox
|
||||
status = "ready"
|
||||
}
|
||||
style_binding = @{
|
||||
style_version_id = $styleVersion.id
|
||||
is_default = $true
|
||||
availability = "enabled"
|
||||
}
|
||||
}
|
||||
if ($SourceSystemCode) {
|
||||
$importBody.source_system_code = $SourceSystemCode
|
||||
}
|
||||
|
||||
$import = Invoke-RsApi -Method "POST" -Path "/api/v1/projects/$project/imports/results" -Body $importBody
|
||||
|
||||
$slice = Invoke-RsApi -Method "POST" -Path ("/api/v1/results/{0}/slice" -f $import.product.product.id) -Body @{
|
||||
requested_by = $RequestedBy
|
||||
build_cog = $true
|
||||
generate_preview = $true
|
||||
generate_legend = $true
|
||||
generate_tiles = $true
|
||||
build_cog_input = @{
|
||||
compression = "deflate"
|
||||
block_size = 256
|
||||
force_rebuild = $true
|
||||
}
|
||||
preview_input = @{
|
||||
style_version_id = $styleVersion.id
|
||||
width = 512
|
||||
height = 512
|
||||
format = "png"
|
||||
force_rebuild = $true
|
||||
}
|
||||
legend_input = @{
|
||||
style_version_id = $styleVersion.id
|
||||
width = 320
|
||||
height = 80
|
||||
format = "png"
|
||||
force_rebuild = $true
|
||||
}
|
||||
tiles_input = @{
|
||||
style_version_id = $styleVersion.id
|
||||
zoom_min = $ZoomMin
|
||||
zoom_max = $ZoomMax
|
||||
tile_size = 256
|
||||
tile_format = "webp"
|
||||
force_rebuild = $true
|
||||
}
|
||||
}
|
||||
|
||||
$jobStates = @()
|
||||
if ($WaitForJobs) {
|
||||
foreach ($job in @($slice.jobs)) {
|
||||
$jobStates += Wait-RsJob -JobID $job.id
|
||||
}
|
||||
}
|
||||
|
||||
$result = Invoke-RsApi -Method "GET" -Path ("/api/v1/results/{0}" -f $import.product.product.id)
|
||||
$delivery = $null
|
||||
if ($WaitForJobs) {
|
||||
$delivery = Invoke-RsApi -Method "GET" -Path ("/api/v1/projects/{0}/products/{1}/delivery?version=latest&style_version_id={2}" -f `
|
||||
(Encode-RsURIComponent $ProjectCode), `
|
||||
(Encode-RsURIComponent $ResultCode), `
|
||||
(Encode-RsURIComponent $styleVersion.id))
|
||||
}
|
||||
|
||||
[ordered]@{
|
||||
run_id = $run.RunID
|
||||
api_base_url = Get-RsApiBaseUrl
|
||||
tile_base_url = Get-RsTileBaseUrl
|
||||
input_path = [System.IO.Path]::GetFullPath($InputPath)
|
||||
result_id = $import.product.product.id
|
||||
result_code = $ResultCode
|
||||
product_version_id = $import.product_version.id
|
||||
style_version_id = $styleVersion.id
|
||||
imported_asset_id = $import.asset.id
|
||||
imported_version_id = $import.asset_version.id
|
||||
slice_job_ids = @($slice.jobs | ForEach-Object { $_.id })
|
||||
slice_job_statuses = @($jobStates | ForEach-Object {
|
||||
[ordered]@{
|
||||
id = $_.id
|
||||
job_type = $_.job_type
|
||||
status = $_.status
|
||||
}
|
||||
})
|
||||
cog_url = if ($delivery) { $delivery.downloads.cog_url } else { $null }
|
||||
preview_url = if ($delivery -and $delivery.preview) { $delivery.preview.url } else { $null }
|
||||
legend_url = if ($delivery -and $delivery.legend) { $delivery.legend.url } else { $null }
|
||||
tileset_url = if ($delivery -and $delivery.tileset) { $delivery.tileset.url_template } else { $null }
|
||||
result_status = $result.status
|
||||
run_directory = $run.RunDirectory
|
||||
log_directory = if ($stack) { $stack.LogDirectory } else { $null }
|
||||
} | ConvertTo-Json -Depth 16
|
||||
}
|
||||
finally {
|
||||
if ($stack -and -not $KeepStack) {
|
||||
Stop-RsBackgroundProcesses -Processes $stack.Processes
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
param(
|
||||
[string]$ProjectCode = "default",
|
||||
[string]$ResultCode = "",
|
||||
[string]$ResultID = "",
|
||||
[string]$TargetSystemCode = "",
|
||||
[string]$CallbackUrl = "",
|
||||
[string]$StyleVersionID = "",
|
||||
[string]$RequestedBy = "demo-user",
|
||||
[bool]$WaitForJob = $true,
|
||||
[bool]$IncludeArtifacts = $true,
|
||||
[int]$TimeoutSec = 180
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "common.ps1")
|
||||
|
||||
Import-RsDemoEnvironment
|
||||
|
||||
if (-not $ResultID -and -not $ResultCode) {
|
||||
throw "ResultID or ResultCode is required"
|
||||
}
|
||||
|
||||
$result = if ($ResultID) {
|
||||
Invoke-RsApi -Method "GET" -Path ("/api/v1/results/{0}" -f $ResultID)
|
||||
} else {
|
||||
Get-RsResultByCode -ProjectCode $ProjectCode -Code $ResultCode
|
||||
}
|
||||
|
||||
if (-not $StyleVersionID) {
|
||||
if ($result.delivery -and $result.delivery.style_version -and $result.delivery.style_version.id) {
|
||||
$StyleVersionID = $result.delivery.style_version.id
|
||||
} elseif ($result.styles -and $result.styles.Count -gt 0) {
|
||||
$StyleVersionID = $result.styles[0].id
|
||||
}
|
||||
}
|
||||
if (-not $StyleVersionID) {
|
||||
throw "No style version is bound to the target result"
|
||||
}
|
||||
|
||||
$body = @{
|
||||
requested_by = $RequestedBy
|
||||
style_version_id = $StyleVersionID
|
||||
include_artifacts = $IncludeArtifacts
|
||||
}
|
||||
if ($TargetSystemCode) {
|
||||
$body.target_system_code = $TargetSystemCode
|
||||
}
|
||||
if ($CallbackUrl) {
|
||||
$body.callback_url = $CallbackUrl
|
||||
}
|
||||
if (-not $TargetSystemCode -and -not $CallbackUrl) {
|
||||
$body.callback_url = "http://127.0.0.1:8920/callback"
|
||||
}
|
||||
|
||||
$push = Invoke-RsApi -Method "POST" -Path ("/api/v1/results/{0}/pushes" -f $result.id) -Body $body
|
||||
|
||||
$job = $null
|
||||
if ($WaitForJob) {
|
||||
$job = Wait-RsJob -JobID $push.job.id -TimeoutSec $TimeoutSec
|
||||
}
|
||||
|
||||
[ordered]@{
|
||||
api_base_url = Get-RsApiBaseUrl
|
||||
result_id = $result.id
|
||||
result_code = $result.code
|
||||
style_version_id = $StyleVersionID
|
||||
target_system_code = $push.target_system_code
|
||||
callback_url = $push.callback_url
|
||||
publish_id = $push.publish_id
|
||||
job_id = $push.job.id
|
||||
job_status = if ($job) { $job.status } else { $push.job.status }
|
||||
resource_kind = $push.resource_kind
|
||||
} | ConvertTo-Json -Depth 8
|
||||
@@ -0,0 +1,27 @@
|
||||
param(
|
||||
[string]$ProjectCode = "default",
|
||||
[string]$Code = "demo_callback",
|
||||
[string]$Name = "Demo Callback Receiver",
|
||||
[string]$CallbackUrl = "http://127.0.0.1:8920/callback",
|
||||
[string]$CallbackMethod = "POST",
|
||||
[int]$TimeoutSeconds = 30,
|
||||
[string]$CreatedBy = "demo-user"
|
||||
)
|
||||
|
||||
. (Join-Path $PSScriptRoot "common.ps1")
|
||||
|
||||
Import-RsDemoEnvironment
|
||||
|
||||
$target = Ensure-RsTargetSystem `
|
||||
-ProjectCode $ProjectCode `
|
||||
-Code $Code `
|
||||
-Name $Name `
|
||||
-CallbackUrl $CallbackUrl `
|
||||
-CallbackMethod $CallbackMethod `
|
||||
-TimeoutSeconds $TimeoutSeconds `
|
||||
-CreatedBy $CreatedBy `
|
||||
-CallbackHeaders @{
|
||||
"X-Demo-System" = "tile-server-demo"
|
||||
}
|
||||
|
||||
$target | ConvertTo-Json -Depth 8
|
||||
@@ -0,0 +1,16 @@
|
||||
@echo off
|
||||
setlocal
|
||||
chcp 65001 >nul 2>&1
|
||||
|
||||
set "ROOT=%~dp0"
|
||||
if "%PYTHON_EXE%"=="" set "PYTHON_EXE=C:\Users\Administrator\.conda\envs\InSAR\python.exe"
|
||||
if "%DEMO_CALLBACK_PORT%"=="" set "DEMO_CALLBACK_PORT=8920"
|
||||
if "%DEMO_CALLBACK_PATH%"=="" set "DEMO_CALLBACK_PATH=/callback"
|
||||
if "%DEMO_CALLBACK_OUTPUT_DIR%"=="" set "DEMO_CALLBACK_OUTPUT_DIR=%ROOT%..\temp\demo-callback"
|
||||
|
||||
if not exist "%PYTHON_EXE%" (
|
||||
echo Python interpreter not found: %PYTHON_EXE%
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
"%PYTHON_EXE%" "%ROOT%demo_callback_receiver.py" --host 127.0.0.1 --port %DEMO_CALLBACK_PORT% --path %DEMO_CALLBACK_PATH% --output-dir "%DEMO_CALLBACK_OUTPUT_DIR%"
|
||||
Reference in New Issue
Block a user