-
Notifications
You must be signed in to change notification settings - Fork 4
Open
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
// Only URL-based fetching is supported
const { collection } = useCollection('collection-id'); // Requires API
const { item } = useItem('https://example.com/item.json'); // Requires URLNo way to load from File objects or FileList.
Desired Behavior
import { useStacValue } from '@developmentseed/stac-react';
function StacFileUploader() {
const [file, setFile] = useState<File | null>(null);
const { value, isLoading, error } = useStacValue({
file: file,
});
return (
<div>
<input
type="file"
accept=".json,application/json"
onChange={(e) => setFile(e.target.files?.[0])}
/>
{value && <StacViewer value={value} />}
</div>
);
}Use Cases from stac-map
stac-map supports file uploads via:
const fileUpload = useFileUpload({ maxFiles: 1 });
const { value, error } = useStacValue({
href,
fileUpload, // Handles uploaded files
});
// In the UI:
<FileUpload.Dropzone>
<Map value={value} />
</FileUpload.Dropzone>Proposed Solution
Enhanced Hook Signature
type UseStacValueOptions = {
// URL-based
url?: string;
// File-based
file?: File;
// Raw JSON
json?: object;
// Fetch options (for URL)
headers?: Record<string, string>;
method?: 'GET' | 'POST';
body?: unknown;
};
function useStacValue(options: UseStacValueOptions): UseStacValueResult;Implementation
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
function FileUploadExample() {
const [file, setFile] = useState<File | null>(null);
const { value, isLoading, error } = useStacValue({ file });
return (
<div>
<input
type="file"
onChange={(e) => setFile(e.target.files?.[0] || null)}
/>
{isLoading && <p>Loading...</p>}
{error && <p>Error: {error.message}</p>}
{value && <pre>{JSON.stringify(value, null, 2)}</pre>}
</div>
);
}2. Drag and Drop
function DragDropExample() {
const [file, setFile] = useState<File | null>(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 (
<div
onDrop={handleDrop}
onDragOver={(e) => e.preventDefault()}
>
Drop STAC JSON file here
{value && <StacViewer value={value} />}
</div>
);
}3. Combined URL and File
function FlexibleLoader({ url, file }) {
// Automatically uses file if provided, otherwise URL
const { value, isLoading } = useStacValue({
url: !file ? url : undefined,
file: file,
});
return <div>{/* ... */}</div>;
}File Validation
Add optional validation:
function validateStacFile(file: File): Promise<boolean> {
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
Metadata
Metadata
Assignees
Labels
No labels