Skip to content

Imagery SDK Guide ​

The Urban Sky SDK now includes built-in support for accessing mission imagery through the Data Explorer API.

Quick Start ​

JavaScript ​

javascript
const { UrbanSkySDK } = require('@urbansky/sdk')

const sdk = await UrbanSkySDK.init({
  apiToken: 'your-api-token',
  baseUrl: 'https://api.ops.urbansky.com',
})

// List missions
const missions = await sdk.listMissions({ page: 1, limit: 50 })

// List images for a mission
const images = await sdk.listImages('mission-id', {
  page: 1,
  limit: 100,
  dateFrom: '2024-06-01T00:00:00Z',
  dateTo: '2024-06-30T23:59:59Z',
  altitudeMin: 15000,
  altitudeMax: 20000,
})

// Get signed URL for original image (valid 15 minutes)
const { url, expiresAt } = await sdk.getImageUrl('mission-id/path/image.jpg', {
  original: 'true',
})

// Get processed image URL
const thumbnail = await sdk.getImageUrl('mission-id/path/image.jpg', {
  w: 400,
  h: 300,
  fit: 'cover',
  format: 'webp',
  quality: 85,
})

// Get image metadata
const metadata = await sdk.getImageMetadata('mission-id/path/image.jpg')

Python ​

python
from urbansky_sdk import (
    UrbanSkySDK,
    ImageListOptions,
    ImageProcessingOptions,
    MissionListOptions
)

sdk = await UrbanSkySDK.init({
    'apiToken': 'your-api-token',
    'baseUrl': 'https://api.ops.urbansky.com'
})

# List missions
missions = sdk.list_missions(MissionListOptions(page=1, limit=50))

# List images for a mission
images = sdk.list_images(
    'mission-id',
    ImageListOptions(
        page=1,
        limit=100,
        date_from='2024-06-01T00:00:00Z',
        date_to='2024-06-30T23:59:59Z',
        altitude_min=15000,
        altitude_max=20000
    )
)

# Get signed URL for original image
signed_url = sdk.get_image_url(
    'mission-id/path/image.jpg',
    ImageProcessingOptions(original='true')
)

# Get processed image URL
thumbnail = sdk.get_image_url(
    'mission-id/path/image.jpg',
    ImageProcessingOptions(
        w=400,
        h=300,
        fit='cover',
        format='webp',
        quality=85
    )
)

# Get image metadata
metadata = sdk.get_image_metadata('mission-id/path/image.jpg')

Complete End-to-End Example ​

This example shows the entire flow from listing missions to downloading an image file. Perfect for testing in the browser console on ops.urbansky.com.

Browser Console (Complete Workflow) ​

javascript
// ============================================
// COMPLETE IMAGE DOWNLOAD WORKFLOW
// ============================================

const API_TOKEN = 'your-32-character-api-token-here'
const BASE_URL = 'https://api.ops.urbansky.com'
const MISSION_ID = 'your-mission-id'

;(async function downloadImageComplete() {
  try {
    console.log('🚀 Starting complete image download flow...\n')

    // STEP 1: List available missions
    console.log('📋 STEP 1: Listing missions...')
    const missionsResponse = await fetch(`${BASE_URL}/v1/images/missions`, {
      headers: {
        'x-api-token': API_TOKEN,
        'Content-Type': 'application/json',
      },
    })

    const missionsData = await missionsResponse.json()
    console.log(`✅ Found ${missionsData.totalCount} missions`)
    console.table(missionsData.missions)

    // STEP 2: List images for specific mission
    console.log(`\n🖼️  STEP 2: Listing images for mission: ${MISSION_ID}`)
    const imagesResponse = await fetch(
      `${BASE_URL}/v1/images/missions/${MISSION_ID}/images?limit=5`,
      {
        headers: {
          'x-api-token': API_TOKEN,
          'Content-Type': 'application/json',
        },
      },
    )

    const imagesData = await imagesResponse.json()
    console.log(
      `✅ Found ${imagesData.totalCount} images (showing first ${imagesData.images.length})`,
    )

    // Show image details
    imagesData.images.forEach((img, i) => {
      console.log(`\n  Image ${i + 1}: ${img.filename}`)
      console.log(`    Location: ${img.latitude}, ${img.longitude}`)
      console.log(`    Altitude: ${img.altitude}m`)
      console.log(`    Size: ${(img.size / 1024 / 1024).toFixed(2)} MB`)
    })

    // STEP 3: Get signed URL for first image
    const firstImage = imagesData.images[0]
    console.log(`\n🔗 STEP 3: Getting signed URL for: ${firstImage.filename}`)

    const signedUrlResponse = await fetch(
      `${BASE_URL}/v1/images?key=${encodeURIComponent(firstImage.key)}&original=true`,
      {
        headers: {
          'x-api-token': API_TOKEN,
          'Content-Type': 'application/json',
        },
      },
    )

    const signedUrlData = await signedUrlResponse.json()
    console.log('✅ Signed URL generated successfully!')
    console.log(`   Expires at: ${signedUrlData.expiresAt}`)

    // STEP 4: Download image from S3
    console.log(`\n💾 STEP 4: Downloading image from S3...`)
    console.log('   Making direct request to S3 (no API token needed!)...')

    const imageResponse = await fetch(signedUrlData.url)
    const blob = await imageResponse.blob()

    console.log(`✅ Image downloaded! Size: ${(blob.size / 1024 / 1024).toFixed(2)} MB`)

    // STEP 5: Save to browser
    console.log(`\n💿 STEP 5: Saving to your computer...`)

    // Create a download link
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = firstImage.filename
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(url)

    console.log(`✅ File saved as: ${firstImage.filename}`)
    console.log('   Check your Downloads folder!')

    // SUMMARY
    console.log('\n' + '='.repeat(50))
    console.log('🎉 COMPLETE! Summary:')
    console.log('='.repeat(50))
    console.log(`✅ API Authentication: Success`)
    console.log(`✅ Missions Found: ${missionsData.totalCount}`)
    console.log(`✅ Images Found: ${imagesData.totalCount}`)
    console.log(`✅ Signed URL Generated: Valid for ~15 minutes`)
    console.log(`✅ File Saved: ${firstImage.filename}`)
    console.log('='.repeat(50))

    return {
      mission: MISSION_ID,
      image: firstImage,
      signedUrl: signedUrlData,
      downloadedSize: blob.size,
    }
  } catch (error) {
    console.error('\n❌ ERROR:', error.message)
    throw error
  }
})()

Expected Output:

🚀 Starting complete image download flow...

📋 STEP 1: Listing missions...
✅ Found 9 missions
┌─────────┬──────────────┬─────────────────┐
│ (index) │      id      │      name       │
├─────────┼──────────────┼─────────────────┤
│    0    │ 'eLHyAqt6fy' │ 'AEROS1'        │
│    1    │ 'raoaiVxYim' │ 'AEROS2'        │
└─────────┴──────────────┴─────────────────┘

🖼️  STEP 2: Listing images for mission: eLHyAqt6fy
✅ Found 234 images (showing first 5)

  Image 1: IMG_0001.jpg
    Location: 39.7392, -104.9903
    Altitude: 15240m
    Size: 5.24 MB

🔗 STEP 3: Getting signed URL for: IMG_0001.jpg
✅ Signed URL generated successfully!
   Expires at: 2024-12-30T18:39:45.000Z

💾 STEP 4: Downloading image from S3...
✅ Image downloaded! Size: 5.24 MB

💿 STEP 5: Saving to your computer...
✅ File saved as: IMG_0001.jpg
   Check your Downloads folder!

==================================================
🎉 COMPLETE! Summary:
==================================================
✅ API Authentication: Success
✅ Missions Found: 9
✅ Images Found: 234
✅ Signed URL Generated: Valid for ~15 minutes
✅ File Saved: IMG_0001.jpg
==================================================

Key Points:

  • ✅ Shows every step in the download process
  • ✅ No external dependencies (works in browser console)
  • ✅ Demonstrates API → Signed URL → S3 download flow
  • ✅ Automatically saves file to Downloads folder
  • ✅ Great for testing and understanding the complete workflow

Core Methods ​

listMissions(options?) ​

List available missions with pagination.

Parameters:

  • options.page (number, optional): Page number (default: 1)
  • options.limit (number, optional): Items per page, max 100 (default: 50)

Returns: MissionListResponse

  • missions: Array of mission objects with id and name
  • totalCount: Total number of missions
  • pagination: Pagination metadata

listImages(missionId, options?) ​

List images for a specific mission with filtering and pagination.

Parameters:

  • missionId (string, required): Mission identifier
  • options.page (number, optional): Page number (default: 1)
  • options.limit (number, optional): Items per page, max 1000 (default: 50)
  • options.dateFrom (string, optional): Start date filter (ISO 8601)
  • options.dateTo (string, optional): End date filter (ISO 8601)
  • options.altitudeMin (number, optional): Minimum altitude in meters
  • options.altitudeMax (number, optional): Maximum altitude in meters
  • options.latitudeMin (number, optional): Minimum latitude (-90 to 90)
  • options.latitudeMax (number, optional): Maximum latitude (-90 to 90)
  • options.longitudeMin (number, optional): Minimum longitude (-180 to 180)
  • options.longitudeMax (number, optional): Maximum longitude (-180 to 180)

Returns: ImageListResponse

  • images: Array of image metadata objects
  • totalCount: Total number of images matching filters
  • pagination: Pagination metadata

getImageUrl(key, options?) ​

Get a pre-signed S3 URL to download or process an image. URLs are valid for approximately 15 minutes.

Parameters:

  • key (string, required): Image key (format: missionId/path/to/image.jpg)
  • options.original (string, optional): Set to 'true' for original unprocessed image
  • options.w (number, optional): Width in pixels
  • options.h (number, optional): Height in pixels
  • options.fit (string, optional): Resize mode: 'cover', 'contain', 'inside', 'outside', 'fill'
  • options.format (string, optional): Output format: 'jpeg', 'png', 'webp', 'avif', 'tiff', 'gif'
  • options.quality (number, optional): Quality 1-100 (higher is better)
  • options.colormap (string, optional): Color map for scientific visualization: 'normal', 'thermal', 'viridis', 'plasma', 'inferno'
  • options.preserveValues (boolean, optional): Preserve measurement values during processing
  • options.displayMode (string, optional): Display mode: 'auto', 'scientific', 'standard'
  • options.targetRange (string, optional): Target range for value mapping

Returns: SignedUrlResponse

  • url: Pre-signed S3 URL (valid ~15 minutes)
  • expiresAt: Expiration timestamp
  • metadata: Additional metadata including the original key

getImageMetadata(key) ​

Get metadata for an image without downloading it.

Parameters:

  • key (string, required): Image key (format: missionId/path/to/image.jpg)

Returns: ImageMetadata

  • key: Image key
  • filename: Original filename
  • timestamp: Capture timestamp (ISO 8601)
  • latitude: Capture latitude (optional)
  • longitude: Capture longitude (optional)
  • altitude: Capture altitude in meters (optional)
  • size: File size in bytes (optional)

Image Processing Options ​

Resize and Format Conversion ​

javascript
// Resize to 800x600 WebP with 85% quality
const url = await sdk.getImageUrl('mission-id/image.jpg', {
  w: 800,
  h: 600,
  fit: 'cover',
  format: 'webp',
  quality: 85,
})

// Create thumbnail maintaining aspect ratio
const thumb = await sdk.getImageUrl('mission-id/image.jpg', {
  w: 400,
  format: 'jpeg',
  quality: 80,
})

Fit Modes:

  • cover: Crop to fill dimensions
  • contain: Fit within dimensions (letterbox)
  • inside: Resize to fit inside dimensions
  • outside: Resize to fit outside dimensions
  • fill: Stretch to fill dimensions

Scientific Visualization ​

javascript
// Apply thermal colormap to temperature data
const thermal = await sdk.getImageUrl('mission-id/thermal-image.tiff', {
  colormap: 'thermal',
  format: 'png',
})

// Preserve exact measurement values
const scientific = await sdk.getImageUrl('mission-id/data-image.tiff', {
  displayMode: 'scientific',
  preserveValues: true,
  format: 'tiff',
})

Available Colormaps:

  • normal: Standard RGB rendering
  • thermal: Hot-to-cold temperature scale
  • viridis: Perceptually uniform, colorblind-friendly
  • plasma: Bright colormap for highlighting
  • inferno: Dark-to-bright intensity scale

Filtering Examples ​

Filter by Date Range ​

javascript
const images = await sdk.listImages('mission-id', {
  dateFrom: '2024-06-01T00:00:00Z',
  dateTo: '2024-06-30T23:59:59Z',
})

Filter by Altitude ​

javascript
const images = await sdk.listImages('mission-id', {
  altitudeMin: 15000, // 15km
  altitudeMax: 20000, // 20km
})

Filter by Geographic Bounding Box ​

javascript
const images = await sdk.listImages('mission-id', {
  latitudeMin: 39.0,
  latitudeMax: 40.0,
  longitudeMin: -105.5,
  longitudeMax: -104.5,
})

Combine Multiple Filters ​

javascript
const images = await sdk.listImages('mission-id', {
  dateFrom: '2024-06-15T00:00:00Z',
  altitudeMin: 15000,
  latitudeMin: 39.0,
  latitudeMax: 40.0,
  page: 1,
  limit: 100,
})

Pagination ​

Results are paginated. Use the pagination metadata to iterate through pages:

javascript
let page = 1
let allImages = []

while (true) {
  const response = await sdk.listImages('mission-id', {
    page,
    limit: 100,
  })

  allImages.push(...response.images)

  if (!response.pagination.hasNextPage) {
    break
  }

  page++
}

console.log(`Total images downloaded: ${allImages.length}`)

Error Handling ​

javascript
try {
  const images = await sdk.listImages('mission-id')
} catch (error) {
  if (error.message.includes('401')) {
    console.error('Authentication failed - check API token')
  } else if (error.message.includes('403')) {
    console.error('Access denied - insufficient permissions')
  } else if (error.message.includes('404')) {
    console.error('Mission not found')
  } else if (error.message.includes('429')) {
    console.error('Rate limit exceeded - slow down requests')
  } else {
    console.error('Unexpected error:', error)
  }
}

Complete Download Example ​

javascript
const https = require('https')
const fs = require('fs')

async function downloadAllImages(missionId, outputDir) {
  let page = 1
  let totalDownloaded = 0

  while (true) {
    const response = await sdk.listImages(missionId, { page, limit: 100 })

    for (const image of response.images) {
      const signedUrl = await sdk.getImageUrl(image.key, { original: 'true' })
      const outputPath = `${outputDir}/${image.filename}`

      await downloadFile(signedUrl.url, outputPath)
      totalDownloaded++

      console.log(`Downloaded ${totalDownloaded}/${response.totalCount}: ${image.filename}`)
    }

    if (!response.pagination.hasNextPage) {
      break
    }

    page++
  }

  return totalDownloaded
}

function downloadFile(url, outputPath) {
  return new Promise((resolve, reject) => {
    https
      .get(url, response => {
        const fileStream = fs.createWriteStream(outputPath)
        response.pipe(fileStream)
        fileStream.on('finish', () => {
          fileStream.close()
          resolve()
        })
        fileStream.on('error', reject)
      })
      .on('error', reject)
  })
}

// Usage
await downloadAllImages('mission-id', './downloads')

TypeScript Support ​

Full TypeScript definitions are included:

typescript
import {
  UrbanSkySDK,
  Mission,
  ImageMetadata,
  ImageListOptions,
  ImageProcessingOptions,
  SignedUrlResponse,
} from '@urbansky/sdk'

const sdk = await UrbanSkySDK.init({
  apiToken: process.env.API_TOKEN!,
  baseUrl: 'https://api.ops.urbansky.com',
})

const missions: Mission[] = (await sdk.listMissions()).missions
const images: ImageMetadata[] = (await sdk.listImages(missions[0].id)).images
const signedUrl: SignedUrlResponse = await sdk.getImageUrl(images[0].key)

Best Practices ​

  1. Respect URL Expiration: Signed URLs expire after ~15 minutes. Download immediately or request a new URL.

  2. Use Appropriate Limits: Request only what you need. Max limits are 100 for missions, 1000 for images.

  3. Add Delays: When downloading many images, add small delays between requests to be respectful:

    javascript
    await new Promise(resolve => setTimeout(resolve, 500)) // 500ms delay
  4. Handle Errors Gracefully: Network issues happen. Implement retry logic with exponential backoff.

  5. Use Processing Options: Instead of downloading large originals and processing locally, use the API's built-in processing.

  6. Cache Metadata: If you need to reference image metadata multiple times, cache it locally rather than requesting repeatedly.

See Also ​