-
Notifications
You must be signed in to change notification settings - Fork 4
Open
Description
Problem
Currently, stac-react assumes you're working with a structured STAC API with known endpoints (/collections, /search, etc.). There's no way to fetch an arbitrary STAC resource (catalog, collection, or item) from any URL without knowing its type in advance.
This is problematic when:
- Following STAC links where the target type is unknown
- Exploring static STAC catalogs
- Working with STAC resources from various sources
- Building generic STAC browsers/explorers
Current Behavior
You must know the resource type and use the specific hook:
// Must know it's a collection
const { collection } = useCollection('collection-id');
// Must know it's an item
const { item } = useItem(itemUrl);
// No way to fetch "whatever is at this URL"Desired Behavior
import { useStacValue } from '@developmentseed/stac-react';
function StacResourceViewer({ url }) {
const { value, type, isLoading, error } = useStacValue(url);
// value could be Catalog, Collection, Item, or ItemCollection
// type indicates what was fetched: 'Catalog' | 'Collection' | 'Feature' | 'FeatureCollection'
switch (type) {
case 'Catalog':
return <CatalogView catalog={value} />;
case 'Collection':
return <CollectionView collection={value} />;
case 'Feature':
return <ItemView item={value} />;
case 'FeatureCollection':
return <ItemCollectionView items={value} />;
}
}Use Cases from stac-map
stac-map has a useStacValue hook that:
const { value, error, items, table } = useStacValue({
href: 'https://any-stac-url.com/resource.json',
fileUpload,
datetimeBounds,
stacGeoparquetItemId,
});
// value is automatically typed based on what was fetched
// Works with URLs, file uploads, and geoparquetProposed Solution
Hook Signature
type StacValueType = 'Catalog' | 'Collection' | 'Feature' | 'FeatureCollection';
type StacValue = StacCatalog | StacCollection | StacItem | StacItemCollection;
type UseStacValueResult = {
value?: StacValue;
type?: StacValueType;
isLoading: boolean;
isFetching: boolean;
error?: ApiErrorType;
refetch: () => Promise<void>;
};
function useStacValue(
url: string,
options?: {
headers?: Record<string, string>;
method?: 'GET' | 'POST';
body?: unknown;
}
): UseStacValueResult;Implementation
import { useQuery } from '@tanstack/react-query';
function useStacValue(url: string, options = {}): UseStacValueResult {
const { data, error, isLoading, isFetching, refetch } = useQuery({
queryKey: ['stac-value', url, options],
queryFn: async () => {
const response = await fetch(url, {
method: options.method || 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
...options.headers,
},
body: options.body ? JSON.stringify(options.body) : undefined,
});
if (!response.ok) {
throw new ApiError(
response.statusText,
response.status,
await response.text(),
response.url
);
}
const json = await response.json();
return {
value: json,
type: determineStacType(json),
};
},
enabled: !!url,
retry: false,
});
return {
value: data?.value,
type: data?.type,
isLoading,
isFetching,
error,
refetch,
};
}
function determineStacType(json: any): StacValueType {
if (json.type === 'Feature') return 'Feature';
if (json.type === 'FeatureCollection') return 'FeatureCollection';
if (json.type === 'Collection') return 'Collection';
if (json.type === 'Catalog') return 'Catalog';
// Fallback: try to infer from structure
if (json.extent && json.license) return 'Collection';
if (json.links && !json.features) return 'Catalog';
throw new Error('Unable to determine STAC type from resource');
}Advanced Features
Type Guards
export function isStacCatalog(value: StacValue): value is StacCatalog {
return value.type === 'Catalog';
}
export function isStacCollection(value: StacValue): value is StacCollection {
return value.type === 'Collection';
}
export function isStacItem(value: StacValue): value is StacItem {
return value.type === 'Feature';
}
export function isStacItemCollection(value: StacValue): value is StacItemCollection {
return value.type === 'FeatureCollection';
}Link Resolution
Optionally resolve relative hrefs to absolute:
function makeHrefsAbsolute(value: StacValue, baseUrl: string): StacValue {
if (!value.links) return value;
return {
...value,
links: value.links.map(link => ({
...link,
href: new URL(link.href, baseUrl).toString(),
})),
};
}Benefits
- ✅ Generic STAC resource fetching
- ✅ Type-safe handling of different STAC types
- ✅ Simplifies link following in STAC catalogs
- ✅ Enables building generic STAC explorers
- ✅ Matches patterns used in other STAC tools
Breaking Changes
None - this is new functionality.
Related Issues
- Add Standalone Hooks (Context-Free Mode) #34 - Add Standalone Hooks (Context-Free Mode)
Testing Requirements
- Test with STAC catalogs
- Test with STAC collections
- Test with STAC items
- Test with item collections
- Test type determination logic
- Test error handling for invalid STAC
- Test with relative and absolute hrefs
Documentation Requirements
- Document the
useStacValuehook - Provide examples for each STAC type
- Document type guards and utilities
- Show link following patterns
Metadata
Metadata
Assignees
Labels
No labels