developmentseed / developmentseed/stac-react
Add File Upload Support
- 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 (
>
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.