Advanced Usage Guide

Direct HTTP access, bulk downloads, and custom applications

Overview

This guide covers advanced usage patterns for Lynker Spatial tiles, including direct HTTP access, programmatic bulk downloads, and custom application integration.

1. Direct HTTP Access with curl

Basic Tile Request

# Get a single tile curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \ "https://tiles.lynker-spatial.com/api/tiles/lynker-spatial-modeling-fabric/5/9/11" \ -o tile.pbf # Check response headers curl -I -H "Authorization: Bearer YOUR_JWT_TOKEN" \ "https://tiles.lynker-spatial.com/api/tiles/lynker-spatial-modeling-fabric/5/9/11"

Downloading Multiple Tiles

#!/bin/bash # Configuration TOKEN="YOUR_JWT_TOKEN" SOURCE="padus" ZOOM=5 OUTPUT_DIR="./tiles" # Create output directory mkdir -p "$OUTPUT_DIR" # Download tiles for specific X range for x in {0..31}; do for y in {0..31}; do URL="https://tiles.lynker-spatial.com/api/tiles/$SOURCE/$ZOOM/$x/$y" OUTPUT="$OUTPUT_DIR/${ZOOM}_${x}_${y}.pbf" echo "Downloading $x/$y..." curl -s -H "Authorization: Bearer $TOKEN" \ "$URL" -o "$OUTPUT" # Check if file is empty (error response) if [ ! -s "$OUTPUT" ]; then echo " Error downloading $ZOOM/$x/$y" rm "$OUTPUT" fi done done echo "Download complete. Files saved to $OUTPUT_DIR"

Batch Download with Parallel Requests

#!/bin/bash # Using GNU parallel for 4 concurrent downloads TOKEN="YOUR_JWT_TOKEN" SOURCE="padus" ZOOM=6 # Generate list of tiles get_tiles() { for x in {0..63}; do for y in {0..63}; do echo "$x $y" done done } # Download function download_tile() { local x=$1 local y=$2 local zoom=$ZOOM local source=$SOURCE local token=$TOKEN URL="https://tiles.lynker-spatial.com/api/tiles/$source/$zoom/$x/$y" OUTPUT="./tiles/${zoom}_${x}_${y}.pbf" curl -s -H "Authorization: Bearer $token" \ "$URL" -o "$OUTPUT" 2>/dev/null echo "Downloaded $zoom/$x/$y" } export -f download_tile export TOKEN SOURCE ZOOM # Run with 4 parallel jobs get_tiles | parallel --jobs 4 'download_tile {1} {2}'

2. Bulk Download API (Python)

Simple Python Downloader

#!/usr/bin/env python3 import requests import os import json from pathlib import Path from concurrent.futures import ThreadPoolExecutor, as_completed class LynkerTileDownloader: def __init__(self, token, base_url="https://tiles.lynker-spatial.com"): self.token = token self.base_url = base_url self.session = requests.Session() self.session.headers.update({ "Authorization": f"Bearer {token}" }) def download_tile(self, source, z, x, y, output_dir): """Download a single tile""" url = f"{self.base_url}/api/tiles/{source}/{z}/{x}/{y}" try: response = self.session.get(url, timeout=10) response.raise_for_status() # Create directory structure tile_dir = Path(output_dir) / source / str(z) / str(x) tile_dir.mkdir(parents=True, exist_ok=True) # Save tile tile_path = tile_dir / f"{y}.pbf" with open(tile_path, 'wb') as f: f.write(response.content) return True, f"Downloaded {source}/{z}/{x}/{y}" except Exception as e: return False, f"Error downloading {source}/{z}/{x}/{y}: {str(e)}" def download_region(self, source, z, bbox, output_dir, max_workers=4): """ Download all tiles in a region bbox: [min_x, min_y, max_x, max_y] """ min_x, min_y, max_x, max_y = bbox with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = [] for x in range(min_x, max_x + 1): for y in range(min_y, max_y + 1): future = executor.submit( self.download_tile, source, z, x, y, output_dir ) futures.append(future) # Process results success_count = 0 for future in as_completed(futures): success, message = future.result() print(message) if success: success_count += 1 return success_count # Usage if __name__ == "__main__": import os from dotenv import load_dotenv load_dotenv() token = os.getenv("LYNKER_TOKEN") downloader = LynkerTileDownloader(token) # Download zoom 5 tiles in a specific region bbox = [8, 10, 12, 14] # x_min, y_min, x_max, y_max success = downloader.download_region( source="padus", z=5, bbox=bbox, output_dir="./tiles", max_workers=4 ) print(f"\\nDownloaded {success} tiles successfully")

3. REST API Queries

List Available Sources

# List available tile sources curl -H "Authorization: Bearer YOUR_TOKEN" \ "https://tiles.lynker-spatial.com/catalog" # Response example: # { # "sources": [ # { # "id": "padus", # "name": "Protected Areas", # "description": "USA Protected Areas Database" # }, # ... # ] # }

Get Metadata (TileJSON)

curl -H "Authorization: Bearer YOUR_TOKEN" \ "https://tiles.lynker-spatial.com/catalog" | jq '.' # Returns TileJSON format with: # - name, description # - min_zoom, max_zoom # - bounds (geographic extent) # - vector_layers (feature types)

Feature Querying

# Query features at a specific tile curl -H "Authorization: Bearer YOUR_TOKEN" \ "https://tiles.lynker-spatial.com/api/tiles/lynker-spatial-modeling-fabric/5/9/11" --output tile.pbf # Returns GeoJSON FeatureCollection with all features in that tile

4. Command-Line GIS Tools

Using GDAL/QGIS OGR Tools

# Convert PMTiles to GeoPackage ogr2ogr -f GPKG output.gpkg \ /vsicurl/https://tiles.lynker-spatial.com/api/tiles/padus/0/0/0 # Reproject to different coordinate system ogr2ogr -f GPKG output_proj.gpkg \ -t_srs EPSG:3857 output.gpkg

Mapbox Vector Tile Tool (mbutil)

# Download MBTiles database mbutil --export padus_tiles.mbtiles ./tiles # Query specific zoom level mbutil --export --zoom-levels 5-8 padus_tiles.mbtiles ./tiles_z5-8

5. Custom Web Application

Express.js Server with Tile Proxy

const express = require('express'); const axios = require('axios'); const rateLimit = require('express-rate-limit'); const NodeCache = require('node-cache'); const app = express(); const cache = new NodeCache({ stdTTL: 3600 }); // Rate limiting (example for a local proxy) // Note: This Express example shows one way to protect a self-hosted proxy // by limiting requests per IP. The production Lynker deployment enforces // monthly data quotas per token (5 GB) and relies on CDN/API protections // (CloudFront, API Gateway, WAF) for request-level throttling; adjust // per-deployment as needed. const limiter = rateLimit({ windowMs: 60 * 1000, // 1 minute max: 100 // limit each IP to 100 requests per windowMs (example) }); app.use('/api/tiles', limiter); // Proxy endpoint app.get('/api/tiles/:source/:z/:x/:y', async (req, res) => { const { source, z, x, y } = req.params; const token = process.env.LYNKER_TOKEN; const cacheKey = `${source}/${z}/${x}/${y}`; // Check cache let data = cache.get(cacheKey); if (data) { res.set('X-Cache', 'HIT'); res.set('Content-Type', 'application/x-protobuf'); return res.send(data); } try { // Fetch from Lynker const response = await axios.get( `https://tiles.lynker-spatial.com/api/tiles/${source}/${z}/${x}/${y}`, { headers: { 'Authorization': `Bearer ${token}` }, responseType: 'arraybuffer', timeout: 10000 } ); // Cache result cache.set(cacheKey, response.data); res.set('X-Cache', 'MISS'); res.set('Content-Type', 'application/x-protobuf'); res.set('Content-Encoding', 'gzip'); res.send(response.data); } catch (error) { const status = error.response?.status || 500; res.status(status).json({ error: 'Failed to fetch tile', message: error.message, source, tile: `${z}/${x}/${y}` }); } }); // Health check app.get('/health', (req, res) => { res.json({ status: 'healthy', uptime: process.uptime(), memory: process.memoryUsage() }); }); app.listen(3000, () => { console.log('Proxy server running on http://localhost:3000'); });

6. Data Processing & Analysis

Converting PMBTiles to Vector Format

#!/usr/bin/env python3 # Convert vector tiles to GeoJSON import json from mapbox_vector_tile import decode def tiles_to_geojson(tiles_dir, output_file): """Convert tiles to GeoJSON""" features = [] for root, dirs, files in os.walk(tiles_dir): for file in files: if not file.endswith('.pbf'): continue path = os.path.join(root, file) with open(path, 'rb') as f: tile_data = f.read() # Decode tile decoded = decode(tile_data) # Extract features for layer_name, layer in decoded.items(): for feature in layer['features']: features.append({ 'type': 'Feature', 'geometry': feature['geometry'], 'properties': feature['properties'], 'layer': layer_name }) # Write GeoJSON with open(output_file, 'w') as f: json.dump({ 'type': 'FeatureCollection', 'features': features }, f) print(f"Wrote {len(features)} features to {output_file}") # Usage tiles_to_geojson('./tiles', 'output.geojson')

7. Performance Optimization

Caching Strategy

Proxy Caching Headers

// In Express.js proxy app.get('/api/tiles/:source/:z/:x/:y.pbf', async (req, res) => { // ... fetch logic ... // Set cache headers res.set('Cache-Control', 'public, max-age=86400'); // 24 hours res.set('ETag', `"${source}-${z}-${x}-${y}"`); res.set('Expires', new Date(Date.now() + 86400000).toUTCString()); res.send(data); });

8. Troubleshooting Advanced Scenarios

Large Tile Downloads Timing Out

# Increase request timeout curl --max-time 300 \ -H "Authorization: Bearer YOUR_TOKEN" \ "https://tiles.lynker-spatial.com/api/tiles/lynker-spatial-modeling-fabric/5/9/11" \ -o tile.pbf # Python: requests.get(url, timeout=300)

Handling Authentication Errors

#!/bin/bash check_auth() { response=$(curl -s -w "\n%{http_code}" \ -H "Authorization: Bearer $1" \ "https://tiles.lynker-spatial.com/catalog") status=$(echo "$response" | tail -n1) if [ "$status" -eq 401 ]; then echo "ERROR: Invalid or expired token" exit 1 elif [ "$status" -eq 403 ]; then echo "ERROR: Insufficient permissions" exit 1 elif [ "$status" -eq 200 ]; then echo "Authentication successful" return 0 else echo "Unexpected status: $status" exit 1 fi } check_auth "YOUR_JWT_TOKEN"

9. Integration with Existing Systems

WMS/WMTS Server Setup

For legacy systems requiring WMS/WMTS, use MapServer or GeoServer to wrap the tiles:

# Example WMS request to local server wrapping Lynker tiles curl "http://local-wms:8080/wms? SERVICE=WMS& VERSION=1.1.1& REQUEST=GetMap& LAYERS=protected_areas& BBOX=-125,25,-66,49& WIDTH=800& HEIGHT=600& SRS=EPSG:4326& FORMAT=image/png"

Resources