developmentseed / developmentseed/stac-react

Add File Upload Support

Open
#36 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
34
Forks
4
PR merge metrics
No merged PRs in 30d

Description

## Problem
stac-react currently only supports fetching STAC resources from URLs. There's no support for:
- Loading STAC JSON from uploaded files
- Exploring local STAC catalogs
- Working with STAC files without hosting them
- Development and testing with local files

Projects like stac-map need to allow users to drag-and-drop or upload STAC JSON files for immediate visualization without requiring the files to be hosted.

## Current Behavior
```typescript
// Only URL-based fetching is supported
const { collection } = useCollection('collection-id'); // Requires API
const { item } = useItem('https://example.com/item.json'); // Requires URL
```

No way to load from File objects or FileList.

## Desired Behavior
```typescript
import { useStacValue } from '@developmentseed/stac-react';

function StacFileUploader() {
const [file, setFile] = useState(null);

const { value, isLoading, error } = useStacValue({
file: file,
});

return (


setFile(e.target.files?.[0])}
/>
{value && }

);
}
```

## Use Cases from stac-map

stac-map supports file uploads via:
```typescript
const fileUpload = useFileUpload({ maxFiles: 1 });

const { value, error } = useStacValue({
href,
fileUpload, // Handles uploaded files
});

// In the UI:

```

## Proposed Solution

### Enhanced Hook Signature
```typescript
type UseStacValueOptions = {
// URL-based
url?: string;

// File-based
file?: File;

// Raw JSON
json?: object;

// Fetch options (for URL)
headers?: Record;
method?: 'GET' | 'POST';
body?: unknown;
};

function useStacValue(options: UseStacValueOptions): UseStacValueResult;
```

### Implementation
```typescript
import { useQuery } from '@tanstack/react-query';

function useStacValue(options: UseStacValueOptions): UseStacValueResult {
const { url, file, json, headers, method, body } = options;

const { data, error, isLoading, isFetching, refetch } = useQuery({
queryKey: ['stac-value', url, file?.name, json, method],
queryFn: async () => {
let value: any;

if (json) {
// Direct JSON object
value = json;
} else if (file) {
// File upload
const text = await file.text();
value = JSON.parse(text);
} else if (url) {
// URL fetch
const response = await fetch(url, {
method: method || 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
...headers,
},
body: body ? JSON.stringify(body) : undefined,
});

if (!response.ok) {
throw new ApiError(
response.statusText,
response.status,
await response.text(),
response.url
);
}

value = await response.json();
} else {
throw new Error('Must provide url, file, or json');
}

return {
value,
type: determineStacType(value),
};
},
enabled: !!(url || file || json),
retry: false,
});

return {
value: data?.value,
type: data?.type,
isLoading,
isFetching,
error,
refetch,
};
}
```

### Example Usage Patterns

#### 1. File Input
```typescript
function FileUploadExample() {
const [file, setFile] = useState(null);
const { value, isLoading, error } = useStacValue({ file });

return (


setFile(e.target.files?.[0] || null)}
/>
{isLoading &&

Loading...

}
{error &&

Error: {error.message}

}
{value &&
{JSON.stringify(value, null, 2)}
}

);
}
```

#### 2. Drag and Drop
```typescript
function DragDropExample() {
const [file, setFile] = useState(null);
const { value } = useStacValue({ file });

const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
const droppedFile = e.dataTransfer.files[0];
if (droppedFile?.type === 'application/json') {
setFile(droppedFile);
}
};

return (

e.preventDefault()}
>
Drop STAC JSON file here
{value && }

);
}
```

#### 3. Combined URL and File
```typescript
function FlexibleLoader({ url, file }) {
// Automatically uses file if provided, otherwise URL
const { value, isLoading } = useStacValue({
url: !file ? url : undefined,
file: file,
});

return

{/* ... */}
;
}
```

## File Validation

Add optional validation:
```typescript
function validateStacFile(file: File): Promise {
return new Promise((resolve) => {
if (!file.name.endsWith('.json')) {
resolve(false);
return;
}

const reader = new FileReader();
reader.onload = (e) => {
try {
const json = JSON.parse(e.target?.result as string);
// Check for STAC required fields
resolve(
json.type &&
json.stac_version &&
json.links
);
} catch {
resolve(false);
}
};
reader.readAsText(file);
});
}
```

## Benefits
- ✅ Load STAC from uploaded files
- ✅ Support drag-and-drop workflows
- ✅ Enable offline/local STAC exploration
- ✅ Simplify development and testing
- ✅ Support in-memory STAC objects
- ✅ Maintain URL-based fetching

## Breaking Changes
None - this extends existing functionality with backward compatibility.

## Testing Requirements
- [ ] Test loading from File objects
- [ ] Test loading from URL
- [ ] Test loading from JSON objects
- [ ] Test error handling for invalid files
- [ ] Test file validation
- [ ] Test with different file sizes
- [ ] Test concurrent file and URL loading

## Documentation Requirements
- [ ] Document file upload patterns
- [ ] Provide drag-and-drop examples
- [ ] Document file validation
- [ ] Show integration with file input libraries
- [ ] Document security considerations

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.