Situation: I am plotting data provided in a GeoTIFF file on a map using Reactjs and geoTiff.js. An example of the GeoTiff files I am using is at the following link: https://drive.google.com/file/d/1yc2Lo5vQrFPAJRe5VklorrvIXuhB1nE0/view.
What I want to do: When the user clicks on a specific state (district or any specific shape) then the data provided in the tiff file should be visible only for that shape. Basically, I want to clip the data in the GeoTIFF file according to a given shape (assuming I have the shape boundary definitions. Further the projection system of the shape boundaries and the GeoTiff file are the same).
Please suggest any approach using georaster, leaflet, georaster-layer-for-leaflet, or some other library.
Here is the code as of now to show whole tiff data on the map.
Code:
import { useEffect, useRef } from "react";
import proj4 from "proj4";
import { useLeafletContext } from "#react-leaflet/core";
import { useMap } from "react-leaflet";
import parseGeoraster from "georaster";
import GeoRasterLayer from "georaster-layer-for-leaflet";
window.proj4 = proj4;
const GeotiffLayer = ({ url, options }) => {
const geoTiffLayerRef = useRef();
const context = useLeafletContext();
const map = useMap();
useEffect(() => {
const container = context.layerContainer || context.map;
fetch(url)
.then((response) => response.arrayBuffer())
.then((arrayBuffer) => {
console.log(arrayBuffer);
parseGeoraster(arrayBuffer).then((georaster) => {
console.log("georaster:", georaster.values[0][1]);
options.georaster = georaster;
geoTiffLayerRef.current = new GeoRasterLayer(options);
container.addLayer(geoTiffLayerRef.current);
map.fitBounds(geoTiffLayerRef.current.getBounds());
});
});
// return () => {
// container.removeLayer(geoTiffLayerRef.current);
// };
}, [context, url, map, options]);
return null;
};
export default GeotiffLayer;
Output:
Fig 1 illustrates the whole tiff file shown on the map.
Fig 2 illustrates the output after a click on the specific state.
What I want to do: When the user clicks on the specific state then tiff data should be shown only for the selected shape, not for other shapes.
Please suggest any approach using georaster, leaflet, georaster-layer-for-leaflet, or some other library.
Related
So basically I have a field where the user can choose an image to upload, how do I make it so that when the user chooses the image, it displays that image on the screen.
Example, the user uploads the plant2.jpeg , how do i display that image on that same screen. My code is as follows:
import FileBase64 from 'react-file-base64'
//State to handle plant Files
const [plantFile , setPlantFile] = useState(null)
//Handle the input onDone for FileBase64
const handleFiles = (files) => {
setPlantFile(files)
}
<FileBase64
multiple = {false}
onDone = {handleFiles}
/>
The FileBase64 returns an object like below:
{
base64: "data:image/jpeg;base64,/9j/2wCEAAMCAgMCAgMDAwMEAw",
name: "plant2.jpeg",
type: "images/jpeg"
}
Is there a way I can display that image? Anyway I can make that happen?
I am using ResponsiveGridLayout, React-Grid-Layout in my application, and I am using echarts as grid items.
The drag and drop works fine, but when i resize the grid item, the chart did not resize together with it. I have tried implementing the onLayoutchange properties, but it is not working.
can someone can help me out here
this is my codesandbox that reproduce the issue
I was able to achieve this, at least when modifying grid items width (not height yet...), by using this hook, then in your chart component :
[...]
const chartRef = useRef<HTMLDivElement>();
const size = useComponentSize(chartRef);
useEffect(() => {
const chart = chartRef.current && echarts.getInstanceByDom(chartRef.current);
if (chart) {
chart.resize();
}
}, [size]);
[...]
return <div ref={chartRef}></div>;
...so your chart will resize when the grid item is resized. I'm not sure about that, still a WIP for me but it works.
Extract this as a custom hook
You can create useEchartResizer.ts, based on #rehooks/component-size :
import useComponentSize from '#rehooks/component-size';
import * as echarts from 'echarts';
import React, { useEffect } from 'react';
export const useEchartResizer = (chartRef: React.MutableRefObject<HTMLDivElement>) => {
const size = useComponentSize(chartRef);
useEffect(() => {
const chart = chartRef.current && echarts.getInstanceByDom(chartRef.current);
if (chart) {
chart.resize();
}
}, [chartRef, size]);
};
Then use it in the component which holds the chart :
export const ComponentWithChart = (props): React.ReactElement => {
const chartRef = useRef<HTMLDivElement>();
useEchartResizer(chartRef);
useEffect(() => {
const chart = echarts.init(chartRef.current, null);
// do not set chart height in options
// but you need to ensure that the containing div is not "flat" (height = 0)
chart.setOption({...} as EChartsOption);
});
return (<div ref={chartRef}></div>);
});
So each time the div is resized, useEchartResizer will trigger a chart.resize(). Works well with react-grid-layout.
I want my TabDashboardDetails.js to find out which chart to be displayed according to the name of the chart fetched from API. In TabDashboardDetails.js I want to replace CogniAreaChart with a component that will have specific view for fetched chart and can also take data from API.
Here is my TabDashboardDetails.js
import React from 'react';
import DefaultScrollView from '../components/default/DefaultScrollView';
import ChartView from '../components/default/ChartView';
import CogniAreaChart from '../components/CogniAreaChart';
import { mapNameToChart } from '../utils/commonFunctions';
import { areaChartData } from '../chartData';
const TabDashboardDetail = ({ navigation, route }) => {
const tabsConfig = route.params.tabsConfig;
const ChartToDispay = mapNameToChart();
return (
<DefaultScrollView>
{tabsConfig.components.map((comp) => {
console.log(tabsConfig.components);
return (
<ChartView key={comp.name} title={comp.name}>
<CogniAreaChart
name={comp.name}
areaChartData={areaChartData}
height={200}
/>
</ChartView>
);
})}
</DefaultScrollView>
);
};
export default TabDashboardDetail;
I want to pick charts from commonfunctions.js that I have used:
/* eslint-disable prettier/prettier */
import {
AreaChart,
BarChart,
LineChart,
PieChart,
SingleCircularProgress,
Histogram,
SimpleTable,
BubbleChart,
CandlestickChart,
SankeyChart,
ScatterPlot,
StackedBarChart,
WaterfallChart,
TreeMap,
MixAndMatch,
SimpleCard,
BlogTable,
LiquidTable,
} from 'react-native-svg-charts';
export const mapNameToChart = (name) => {
const nameToChart = {
AreaChart: AreaChart,
BarGraph: BarChart,
LineChart: LineChart,
PieChart: PieChart,
SingleCircularProgress: SingleCircularProgress,
Histogram: Histogram,
SimpleTable: SimpleTable,
BubbleChart: BubbleChart,
CandlestickChart: CandlestickChart,
SankeyChart: SankeyChart,
ScatterPlot: ScatterPlot,
StackedBarGraph: StackedBarChart,
WaterfallTable: WaterfallChart,
TreeMap: TreeMap,
MixAndMatch: MixAndMatch,
SimpleCard: SimpleCard,
BlogCard: BlogTable,
LiquidGauge: LiquidTable,
};
return nameToChart[name];
};
You first need to import all the chart types in the file containing mapNameToChart and map the name to the Chart type accordingly. Then You can try this
const ChartToDispay = mapNameToChart(name);
<ChartToDisplay {...your_props_here} />
In your mapNameToChart function it looks like AreaChart is an actual component and the rest are just string names of components instead of the components themselves. You want to change it so that all of the entries in the map are the components. You want mapNameToChart(name) to return a callable component. Then you can call that component with your props.
I'm not fully understanding your the API comes into play here, but it seems like we get the props by looking up the name? I don't know the the API data comes from, so I'm expecting the components array to be passed as a prop to the CustomChart.
const CustomChart = ({name, components, ...props}) => {
// get the component function/class from your map
const Component = mapNameToChart(chart);
// find the component configuration from your API
const config = components.find(obj => obj.name === name);
// call with props from the config and passed down props
return (
<Component
{...config}
{...props}
/>
)
}
Is there a simple way to animate the Polygon element from the react-native-svg library?
I need to animate his shape by animating the points.
I found few examples on how to animate Path element or Circle, but couldn't find anything regarding the Polygon. Thanks in advance.
Bit late to the party, but I've found a solution if you're still interested. It's not exactly 'simple', but it works. There's a library called React Native Reanimated, and it extends the functionality of Animated components
substantially. Here's what I was able to achieve:
The reason animating Polygons isn't available out of the box is because the standard Animated API only handles simple values, namely individual numbers. The Polygon component in react-native-svg takes props of points, which is an array of each of the points, themselves array of x and y. For example:
<Polygon
strokeWidth={1}
stroke={strokeColour}
fill={fillColour}
fillOpacity={1}
points={[[firstPointX, firstPointY],[secondPointX, secondPointY]}
/>
React Native Reanimated allows you to animate even complex data types. In this case, there is useSharedValue, which functions almost identical to new Animated.value(), and a function called useAnimatedProps, where you can create your points (or whatever else you want to animate) and pass them to the component.
// import from the library
import Animated, {
useSharedValue,
useAnimatedProps,
} from 'react-native-reanimated';
// creates the animated component
const AnimatedPolygon = Animated.createAnimatedComponent(Polygon);
const animatedPointsValues = [
{x: useSharedValue(firstXValue), y: useSharedValue(firstYValue)},
{x: useSharedValue(secondXValue), y: useSharedValue(secondYValue)},
];
const animatedProps = useAnimatedProps(() => ({
points: data.map((_, i) => {
return [
animatedPointValues[i].x.value,
animatedPointValues[i].y.value,
];
}),
})),
Then in your render/return:
<AnimatedPolygon
strokeWidth={1}
stroke={strokeColour}
fill={fillColour}
fillOpacity={1}
animatedProps={animatedProps}
/>
Then whenever you update one of those shared values, the component will animate.
I'd recommend reading their docs and becoming familiar with the library, as it will open up a whole world of possibilities:
https://docs.swmansion.com/react-native-reanimated/
Also, the animations are handled in the native UI thread, and easily hit 60fps, yet you can write them in JS.
Good luck!
react-native-reanimated also supports flat arrays for the Polygon points prop, so we can simplify the animation setup even more.
Full example which will animate the react-native-svg's Polygon when the points prop changes looks like this:
import React, { useEffect } from 'react'
import Animated, { useAnimatedProps, useSharedValue, withTiming } from 'react-native-reanimated'
import { Polygon } from 'react-native-svg'
interface Props {
points: number[]
}
const AnimatedPolygonInternal = Animated.createAnimatedComponent(Polygon)
export const AnimatedPolygon: React.FC<Props> = ({ points }: Props) => {
const sharedPoints = useSharedValue(points)
useEffect(() => {
sharedPoints.value = withTiming(points)
}, [points, sharedPoints])
const animatedProps = useAnimatedProps(() => ({
points: sharedPoints.value,
}))
return <AnimatedPolygonInternal fill="lime" animatedProps={animatedProps} />
}
I am trying to use the forge viewer with markups extension inside of a react app and have ran into a problem.
The viewer is docked within a sheet that slides out from the right handside of the page. The first time I open the viewer it works fine, I click the markup icon and can draw an arrow e.g.:
When I reopen the same document, click the markup icon and draw another arrow, the arrow is huge, like the scale is all wrong:
This is the full react component so far
import React, { useRef, useEffect } from 'react';
import { ReadonlyFormHTMLContainer } from '../../Components/Form/FormComponents';
import { useGlobalContext } from '../../GlobalState';
type ForgeViewerProps = {
id: string;
fileUrl: string;
filename: string;
};
let viewer: Autodesk.Viewing.Viewer3D | null | undefined;
// I can't find a type for Autodesk.Viewing.MarkupsCore - if you can find it, replace any with the correct type, if not you are on your own, no intellisense!
let markup: any;
export const ForgeViewer = (props: ForgeViewerProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const context = useGlobalContext();
useEffect(() => {
window.Autodesk.Viewing.Initializer({ env: 'Local', useADP: false }, () => {
viewer = new window.Autodesk.Viewing.GuiViewer3D(containerRef.current!);
viewer.start();
viewer.loadExtension('Autodesk.PDF').then(() => {
viewer!.loadModel(props.fileUrl, viewer!);
viewer!.loadExtension('Autodesk.Viewing.MarkupsCore').then(ext => {
markup = ext;
});
viewer!.loadExtension('Autodesk.Viewing.MarkupsGui');
});
});
return () => {
console.log('Running clean up');
viewer?.tearDown();
viewer?.finish();
viewer = null;
};
}, []);
return (
<ReadonlyFormHTMLContainer
title={'Document markup'}
subtitle={props.filename}
formErrorMessage=""
formErrorTitle=""
onClose={() => context.hideSheet()}
>
<div ref={containerRef}></div>{' '}
</ReadonlyFormHTMLContainer>
);
};
I have imported following modules from npm:
Forge version: https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.js
Does anyone have any idea on how to resolve this? As you can see I've tried some clean up code when the component unmounts but I cannot get the viewer to work "normally" after the initial open.
Edit
repro link: https://github.com/philwindsor/forge-repro/blob/master/index.html
This is a timing issue.
When you open the viewer for the first time, the markup extension takes some time to download and it is therefore initialized after the model has already been loaded. Because of that, the extension knows how to initialize the scale of its markups properly.
When you open the viewer for the second time, the markup extension is already available, and it is loaded and initialized before any model is available. And because of that, it cannot configure the "expected" scale.
To resolve the issue, simply load the Autodesk.Viewing.MarkupsCore and Autodesk.Viewing.MarkupsGui extensions after the model is loaded, for example:
viewer.loadModel(urn, options, function onSuccess() {
viewer.loadExtension("Autodesk.Viewing.MarkupsCore");
viewer.loadExtension("Autodesk.Viewing.MarkupsGui");
});