agvmap-react
v0.6.0
Published
A tool designed to visualize geospatial data on a map, integrating custom layers fetched from a REST API, and displaying relevant time-series data. This project is built using React, with `react-map-gl` for map integration.
Maintainers
Readme
Geospatial Data Visualization Tool
A tool designed to visualize geospatial data on a map, integrating custom layers fetched from a REST API, and displaying relevant time-series data. This project is built using React, with react-map-gl for map integration.
Project Setup
To run this project locally, you'll need the following:
- MapTiler API Key for basemap style
- Sentinel Hub Client ID and Secret for fetching data and imagery from sentinel hubs
the following environment variables are required:
VITE_MAPTILER_ACCESS_KEY=your-maptiler-key
VITE_SENTINAL_HUB_CLIENT_ID=your-sentinal-hub-api-key
VITE_SENTINAL_HUB_CLIENT_SECRET=your-sentinal-hub-client-secret
VITE_SENTINAL_HUB_WMTS_ID=sentinal-hub-wmts-layer-id
VITE_AGV_API_USERNAME=your_username
VITE_AGV_API_PASSWORD=your_passwordSteps to Setup:
- Rename
env.exampleto.env. - Paste the API keys and credentials into the
.envfile. - in the project directory, run
yarn installin order to install required packages - run
yarn devto start the development server - goto
localhost:3000and you shall see the app running.
USage example
### Composable usage
The default `AgvMap` component remains available. For customized layouts, use
the compound API. The exported map parts automatically use the active map ref
and store state, so they generally do not require internal context wiring:
```jsx
import * as Agvmap from 'agviewermap-react';
import 'agviewermap-react/style.css';
<Agvmap.Root configs={configs} authToken={authToken}>
<Agvmap.Providers markers={markers}>
<Agvmap.themeProvider configs={configs}>
<Agvmap.Map style={style} configs={configs}>
<Agvmap.NDVILayer />
<Agvmap.LayerDataViewer />
<Agvmap.ColorLegend />
<Agvmap.BottomControls />
<Agvmap.Markers />
<Agvmap.MarkerPopup />
<Agvmap.Farms />
<Agvmap.Plots />
<Agvmap.NavbarWrapper />
</Agvmap.Map>
</Agvmap.themeProvider>
</Agvmap.Providers>
</Agvmap.Root>Passing children to Agvmap.Map opts into a custom map layout. Omitting
children preserves the existing complete layout. Agvmap.ThemeProvider is
also available as a conventional capitalized alias for themeProvider.
For example, a more complete custom layout can be assembled like this:
import * as Agvmap from 'agviewermap-react';
import 'agviewermap-react/style.css';
function CustomMap({ configs, authToken, markers, style }) {
return (
<div style={{ height: '100vh' }}>
<Agvmap.Root configs={configs} authToken={authToken}>
<Agvmap.Providers markers={markers}>
<Agvmap.themeProvider configs={configs}>
<Agvmap.Map style={style} configs={configs}>
<Agvmap.MapRasterLayer />
<Agvmap.NavbarWrapper />
<Agvmap.MapDateSelector />
<Agvmap.MapControl />
<Agvmap.MapPickerControl />
<Agvmap.LayerDataViewer />
<Agvmap.ColorLegend />
<Agvmap.BottomControls />
<Agvmap.MapEditingControls />
<Agvmap.Markers />
<Agvmap.MarkerPopup />
<Agvmap.MapFarms />
<Agvmap.MapPlots />
<Agvmap.StatusBar />
<Agvmap.MapLoadingOverlay />
<Agvmap.AreaDetails />
<Agvmap.ComparisonMap />
</Agvmap.Map>
</Agvmap.themeProvider>
</Agvmap.Providers>
</Agvmap.Root>
</div>
);
}Important: once children are passed to Agvmap.Map, they replace the default
internal layout. Add only the parts your application needs, or use the
original AgvMap component when you want the complete built-in experience.
Public hooks for custom components
Custom children can read and control the data used by the map through the
public hooks below. They must render inside Agvmap.Providers (normally as a
child of Agvmap.Map). Hooks throw a helpful error when used outside the
provider tree.
import {
useMapContext,
useMapState,
useFarms,
usePlots,
useMarkers,
useRasterLayer,
} from 'agviewermap-react';
function NavigationAndDataPanel() {
const { mapRef } = useMapContext();
const { farms, loading: farmsLoading } = useFarms();
const { plots, setClickedPlot } = usePlots();
const { markers } = useMarkers();
const { layer, opacity, setOpacity } = useRasterLayer();
const dateRange = useMapState((state) => state.dateRange);
const map = mapRef.current?.getMap?.();
return (
<button onClick={() => map?.fitBounds(/* bounds */)}>
{farmsLoading ? 'Loading farms…' : `${farms.length} farms`}
</button>
);
}The complete public hook list is: useMapContext, useMapState, useFarms,
usePlots, useMarkers, useRasterLayer, useSettings, useInitialView,
useDarkMode, useUserPreferences, useConfirm, and useTour.
useMapState(selector) supports Zustand-style selectors, so prefer selecting
one field instead of subscribing to the entire store. The returned farm, plot,
marker, and raster objects include their current data, loading flags, setters,
and supported actions. These hooks are the supported public API; consumers
should not import files from the package's internal contexts, hooks, or
stores directories.
Hook reference
All data hooks below must be called from a component rendered inside
<Agvmap.Providers>. The values update reactively when the map application
state changes.
useMapContext() returns the map-level refs and interaction state:
| Property | Description |
| --- | --- |
| mapRef | Ref to the react-map-gl map wrapper. Use mapRef.current?.getMap() to get the underlying MapLibre instance. |
| drawRef | Ref to the Mapbox Draw instance when drawing is enabled. |
| mapContainerRef | Ref to the map container element. |
| sources | Application-managed map sources. |
| setSources(value) | Replace the application-managed sources object. |
| mode / setMode(value) | General map interaction mode. |
| status / setStatus(value) | Map operation status. |
| showDrawActionPopup / setShowDrawActionPopup(value) | Draw-action popup state. |
| isDetailActive / setIsDetailActive(value) | Detail-panel state. |
Example:
function RecenterButton({ longitude, latitude, zoom = 12 }) {
const { mapRef } = useMapContext();
return (
<button
onClick={() => mapRef.current?.getMap()?.flyTo({
center: [longitude, latitude],
zoom,
essential: true,
})}
>
Recenter
</button>
);
}useFarms() returns:
| Property | Description |
| --- | --- |
| farms | Current farm records. |
| farmOptions | Farm options prepared for selectors. |
| loading | Whether farm data or an operation is loading. |
| refreshFarms() | Reload farms. |
| addNewFarm(name, markerSet, seasonStartDate, geometry, weatherStation, unitSystem, preferredLandcoverClass) | Create a farm. Returns a promise. |
| handleFarmUpdate(farmId, formValues) | Update a farm. Returns a promise. |
| handleDeleteFarm(farmId) | Delete a farm. Returns a promise. |
| getFarmsList() | Retrieve the farm list. |
| getFarmOptionsList() | Retrieve selector options. |
usePlots() returns:
| Property | Description |
| --- | --- |
| plots | Current plot records. |
| loading | Whether plot data or an operation is loading. |
| clickedPlot / setClickedPlot(plot) | Currently selected plot. |
| editingPlot | Plot currently being edited. |
| showPlots / setShowPlots(value) | Plot visibility. |
| addNewPlot(...) | Create a plot. The exact arguments follow the configured plot API. |
| handlePlotUpdate(...) | Update a plot. |
| handleDeletePlot(plot) | Delete a plot. Returns a promise. |
| handleFlyToPlot(coordinates) | Fit the map to plot coordinates. |
| handleEditPlot(plot) | Start editing a plot. |
| weeksBefore / setWeeksBefore(value) | Historical comparison setting. |
| showNdviLayer | Whether the NDVI layer is shown. |
| setNDVILayersVisibility(value) | Set NDVI visibility to visible or none. |
| toggleNDVILayersVisibility(value?) | Toggle or explicitly set NDVI visibility. |
| changeNdviLayerOpacity(value) | Set NDVI opacity from 0 to 100. |
useMarkers() returns:
| Property | Description |
| --- | --- |
| markers / markersData | Current marker records. |
| unfilteredMarkers | Marker records before filters are applied. |
| loading | Whether marker data or an operation is loading. |
| showMarkers / setShowMarkers(value) | Marker visibility. |
| clickedMarker / setClickedMarker(marker) | Currently selected marker. |
| markerFilters / setMarkerFilters(value) | Active marker filters. |
| resetFilters() | Clear marker filters. |
| addNewMarker(...) | Create a marker. |
| handleMarkerUpdate(...) | Update a marker. |
| handleDeleteMarker(...) | Delete a marker. |
useRasterLayer() returns:
| Property | Description |
| --- | --- |
| layer / setLayer(value) | Active raster layer option. |
| layerOptions | Available raster layer options. |
| opacity / setOpacity(value) | Raster opacity, normally 0–100. |
| handleOpacityChange(event) | Form-event handler for opacity controls. |
| isVisible / setIsVisible(value) | Raster visibility. |
| isDetailActive | Raster detail state. |
| datesLoading / setDatesLoading(value) | Satellite-date loading state. |
Shared map state
Use useMapState for cross-feature state such as date ranges, view modes,
cursor state, selected data, and loading state. The store currently includes
these commonly used fields:
| Field | Description |
| --- | --- |
| dateRange, dateRange2 | Selected date ranges, each with start and end Dates. |
| setDateRange(range), setDateRange2(range) | Update date ranges. |
| viewMode | Current interaction mode. |
| setViewMode(mode) | Set interaction mode. |
| mapMode | Normal or comparison map mode. |
| rasterLayer, rasterOpacity | Shared raster selection and opacity. |
| pickerData, cursorCords | Picker result and pointer coordinates. |
| hoveredPlot, hoveredFarmId, clickedMarker | Current hover/selection state. |
| sidebarExpanded | Sidebar visibility state. |
| loadingNDVIImages, datesLoading, rasterLayerLoading | Loading state. |
| configs | Runtime configuration passed to the map. |
The store also provides corresponding setters and mode helpers such as
toNormalMode(), toPickerMode(), toDrawMode(), setCursor(), and
resetCursor(). Because this store can grow as features are added, consumers
should select only the fields they use:
const dateRange = useMapState((state) => state.dateRange);
const setViewMode = useMapState((state) => state.setViewMode);Settings and supporting hooks
useSettings() returns { settings, setSettings }. settings contains the
active basemap configuration and map settings loaded from the backend.
useInitialView() returns either null while settings are loading or an
object containing { latitude, longitude, zoom }.
useDarkMode() returns the host system's dark-mode preference as a boolean.
useUserPreferences() returns the persisted user-preference state and actions
used by the map. Its exact fields may depend on the configured preferences.
useConfirm() returns the confirmation state tuple:
const [confirm, setConfirm] = useConfirm();
// confirm = { prompt, isOpen, proceed, cancel }useTour() returns the tour context, including current step, completion and
visibility state, and tour navigation actions. It is intended for custom tour
controls and integrations.
Data shape and forward compatibility
Farm, plot, and marker records are API/domain objects and may contain fields specific to the deployment. Consumers should use the documented top-level collections and actions, and treat individual record fields as domain data rather than depending on undocumented UI internals. The package's TypeScript declarations expose the hook return values and can be augmented in the host application with its own farm, plot, and marker interfaces.
The following feature components are available for custom layouts:
NDVILayerLayerDataViewerColorLegendBottomControlsMarkersMarkerPopupFarmsPlotsNavbarWrapperDateHierarchyDropdownV2PAWStatusPieChartDrawPolygonControlEditPlotGeometryControlAddPlotControlAddStationControlAddFarmModalPickerControlAreaDetailsStatusBarMapLoadingOverlayFarmETProgressIndicatorMapControl
Self-contained map parts (recommended)
These components wrap the feature parts above and own their positioning and
conditional logic — mount them inside Agvmap.Map (or Agvmap.ComparisonMap)
and they self-configure to the nearest map instance:
MapRasterLayer— raster/NDVI layer (showCroppedImagesgating + store dateRange)MapDateSelector— top-left date selector (variant="single" | "split")MapControl— right-side control stack (zoom, fullscreen, split view, draw, picker)MapPickerControl— floating picker toggle (shown in comparison view)MapEditingControls— drawing/editing overlays byviewModeMapPlots/MapFarms— plots and farms bound to the nearest mapComparisonMap— self-contained split/comparison second map
Most take an optional variant prop: "single" (default, binds to dateRange)
or "split" (binds to dateRange2). All are exported from the package root.
Map parts entry (agvmap-react/parts)
For consumer systems that want to include only the pieces they need, the
package exposes a curated agvmap-react/parts entry. It exports every
mountable map component — self-contained wrappers, positioning wrappers,
and context-connected leaf renderers — so you can compose a map layout from
just the parts your application needs.
import {
MapControl,
MapDateSelector,
MapPlots,
Markers,
} from 'agvmap-react/parts';When you pass children to Agvmap.Map, they replace the default
internal layout, so you control exactly what renders:
import * as Agvmap from 'agviewermap-react';
import {
MapControl,
MapPlots,
MapDateSelector,
Markers,
} from 'agviewermap-react/parts';
// Example 1 — a minimal map with just drawing capability
function DrawOnlyMap({ configs }) {
return (
<Agvmap.Map style={{ height: '100%' }} configs={configs}>
<MapControl /> {/* zoom, draw, picker toolbar */}
<Agvmap.MapEditingControls /> {/* edit/add plot & station overlays */}
</Agvmap.Map>
);
}
// Example 2 — a map focused on markers only
function MarkersOnlyMap({ configs, markers }) {
return (
<Agvmap.Map style={{ height: '100%' }} configs={configs}>
<Agvmap.Markers />
<Agvmap.MarkerPopup />
</Agvmap.Map>
);
}
// Example 3 — full custom layout with date navigation and plots
function FullCustomMap({ configs }) {
return (
<Agvmap.Map style={{ height: '100%' }} configs={configs}>
<Agvmap.MapRasterLayer />
<Agvmap.MapDateSelector />
<MapControl />
<Agvmap.MapPlots />
<Agvmap.MapFarms />
<Agvmap.StatusBar />
<Agvmap.MapLoadingOverlay />
</Agvmap.Map>
);
}Note:
Agvmap.Mapitself must wrap your parts so the required providers (Agvmap.Root,Agvmap.Providers,Agvmap.themeProvider) are mounted.Agvmap.ComparisonMapcan be mounted inside as the split-view second map.
The parts entry is type-safe: agvmap-react/parts resolves to
types/parts.d.ts, which re-exports the same component types as the package
root.
The complete layout can be rebuilt from the exported parts, including
DateHierarchyDropdownV2, PAWStatusPieChart, DrawPolygonControl,
EditPlotGeometryControl, AddPlotControl, AddStationControl,
AddFarmModal, PickerControl, AreaDetails, StatusBar,
MapLoadingOverlay, FarmETProgressIndicator, and MapControl.
<AgvMap
configs={{
VITE_MAPTILER_ACCESS_KEY: your-value,
VITE_SENTINAL_HUB_CLIENT_ID: your-value,
VITE_SENTINAL_HUB_CLIENT_SECRET: your-value,
VITE_SENTINAL_HUB_WMTS_ID: your-value,
}}
requestHeaders={{ Authentication: your-value }}
## Project Goal
Implement a map component with custom layers fetched from a REST API, to be integrated into a dashboard for visualization and analysis.
## Observations
- A marker may belong to a farm, and a farm can have multiple markers.
- a farm can have many plots
## FAQs
- **Why does the backend throw a CORS error?**
- Only port 3000 is allowed on the backend.
- **What is the difference between forecast markers and station markers?**
- If a marker is associated with a device, it represents a station marker. Forecast markers, on the other hand, are not associated with devices, and their data is fetched from a third-party service.