here I have a question regarding references to creating a feature in the react leaflet.
So, the feature has the following functions =
The user page when they want to upload a location is in the form of a csv file containing latitude and longitude.
When the user clicks the red button above, a popup will appear to upload the csv file.
When finished uploading the csv file, it will go directly to the location based on latitude and longitude.
So my question is, does anyone have a tutorial on how to create a csv upload button that points directly to a map with reactjs and leaflets? Thank you very much
Although you have not asked to use react-leaflet I would advise you to do so because you will end up in a mess when you will have to export the map reference to reuse it across places.
First create a button that will handle the upload of a csv file. There is a really useful guide to do so without the use of libraries like paparse although it simplifies a lot this procedure. Next you need to transform the csv columns to some form of data to use. This is also included in the guide. You end up with an array of csv columns.
Then all you need to do is to create a custom react-leaflet component to render the markers and zoom to the markers viewport.
Also you can clean the csv file once you insert a new one.
function App() {
const [csvToArray, setCsvToArray] = useState([]);
const fileReader = new FileReader();
const csvFileToArray = (string) => {
const csvHeader = string.slice(0, string.indexOf("\n")).split(",");
const csvRows = string.slice(string.indexOf("\n") + 1).split("\n");
const array = csvRows.map((i) => {
const values = i.split(",");
const obj = csvHeader.reduce((object, header, index) => {
object[header] = values[index];
return object;
}, {});
return obj;
});
setCsvToArray(array);
};
const handleOnChange = (e) => {
if (csvFileToArray.length > 0) setCsvToArray([]);
const file = e.target.files[0];
if (file) {
fileReader.onload = function (event) {
const text = event.target.result;
csvFileToArray(text);
};
fileReader.readAsText(file);
}
};
console.log(csvToArray);
return (
<>
<input
type="file"
id="csvFileInput"
accept=".csv"
onChange={handleOnChange}
/>
<Map csvToArray={csvToArray} />
</>
);
}
function RenderCsvToArray({ csvToArray }) {
const map = useMap();
useEffect(() => {
if (csvToArray.length === 0) return;
const myPoints = csvToArray.map(({ Latitude, Longitude }) => [
Latitude,
Longitude
]);
const myBounds = new L.LatLngBounds(myPoints);
map.fitBounds(myBounds);
}, [csvToArray]);
return csvToArray?.map(({ Latitude, Longitude, Title }, index) => (
<Marker key={index} icon={icon} position={[Latitude, Longitude]}>
<Popup>{Title}</Popup>
</Marker>
));
}
You can see the full implementation on the demo
I have also inlcuded two csv files to play with in the form of
Title,Latitude,Longitude
Trinity College,41.745167,-72.69263
Wesleyan University,41.55709,-72.65691
and
Group,Title,Image,Description,Address,Latitude,Longitude
a,Trinity College,https://www.edx.org/sites/default/files/trinity1.jpg,"Not in the link view website more not in the link","300 Summit St - Hartford CT 06106,41.745167,-72.69263
a,Wesleyan University,https://upload.wikimedia.org/wikipedia/commons/4/41/You_are_here_-_T-shirt.jpg,"view website",45 Wyllys Ave Middletown CT 06459,41.55709,-72.65691
Related
I met an issue at Ant Design <Upload /> component. When I upload an image it sends two Img typed requests at the Network console. First contains my orginal image (tested at 650x650 image size), second contains transformed image to 200x200. And here is my question: Why image is rescaled to lower resolution and how can I prevent that? I want to upload orignal images with transparency if that is required.
In my case Upload component is used to upload single image. Component is placed on modal component that is why I prepared dummyRequest.
const UploadImage = ({
defaultValue,
maxFilesAmount,
}: IUploadImageProps<any>): JSX.Element => {
const [fileList, setFileList] = useState<IUploadFile<any>[]>(
defaultValue ?? []
)
const onChange = useCallback(
(info: IUploadChangeParam<IUploadFile<unknown>>): void => {
setFileList(info.fileList)
},
[setFileList]
)
const onPreview = useCallback(async (file: IUploadFile): Promise<void> => {
let src: string | undefined = file?.url ?? file?.thumbUrl ?? ''
if (!_.isNull(src)) {
src = await convertImportImageToBase64(src)
}
const image: HTMLImageElement = new Image()
const imgWindow: Window | null = window.open(src)
image.src = src
imgWindow?.document.write(image.outerHTML)
}, [])
const dummyRequest = useCallback(({ onSuccess }: IUploadRequestOption) => {
setTimeout(() => {
//#ts-ignore
onSuccess('ok')
}, 0)
}, [])
const beforeUpload = useCallback((): false => {
//Required to ignore Ant Desing default converting uploaded image
//For example that removes transparency from png files
return false
}, [])
return (
<Upload
accept={'.jpg, .jpeg, .png'}
listType={'picture-card'}
beforeUpload={beforeUpload}
fileList={fileList}
onChange={onChange}
onPreview={onPreview}
customRequest={dummyRequest}
>
{_.size(fileList) < (maxFilesAmount ?? 1) && 'Upload'}
</Upload>
)
}
At the start I had also a problem with losing transparency at .png images. Image still was defined as png but transparent background got a white color. Used beforeUpload fix that, I not sure why but okej :).
What can I do more to fix this issue? Thanks in advance.
More specifically, it causes the same code to render an outline on the map, and only recognizes the last path element when using pointer events.
I simplified a continents.json file on mapshaper using their website and replaced the file in my public/data folder. Currently I'm fetching the data in one component and passing it down to another.
const WorldMapAtlas = () => {
const [continents, continents] = useState<null | FeatureCollection>(null)
useEffect(() => {
fetch('data/continents_simple.json')
.then(response => response.json())
.then((worldData) => {
let continents: FeatureCollection = (worldData as FeatureCollection)
setContinents(continents)
})
.catch(error => console.log(error))
}, [])
return (
<>
{continents &&
<div className="w-3/4" >
<TestMap projectionType={geoNaturalEarth1} GeoJson={continents} size={[928, 454]} translate={[464, 227]} />
</div>
}
</>
)
}
I then try to render it with the TestMap Component
interface props {
projectionType: () => GeoProjection;
GeoJson: FeatureCollection | null;
size: [number, number];
translate: [number, number];
}
const TestMap = (props: props) => {
const svgRef = useRef(null)
const [height, width] = props.size;
useEffect(() => {
//accesses the reference element
const svg = d3.select(svgRef.current)
//declares the geoJson, projection, and path(pathGenerator)
const geoJson = props.GeoJson
const projection = props.projectionType()
.fitSize(props.size, geoGraticule10())
.translate(props.translate)
const path = d3.geoPath().projection(projection)
//uses d3 to inject the data into the svg element
const features = svg.selectAll(".country")
.data(geoJson ? geoJson.features : [])
.enter().append("path")
//basic styling attributes
.attr("d", path)
.attr("fill", "none")
.attr("stroke", "aliceblue")
.attr("stroke-width", "0.5px")
//allows pointer events anywhere inside the path even when fill=none
.attr("pointer-events", "visibleFill")
.attr("visibility", "visible")
//sets the path element id to the continent name
.attr("id", (d) => {
return `${d.properties.continent}`
})
//selects the current continent and highlights it by increasing the stroke width
.on("mouseover", (d,i) => {
svg.select(`#${i.properties.continent}`).attr("stroke-width", "2px")
})
//deselects
.on("mouseleave", (d,i) => {
svg.select(`#${i.properties.continent}`).attr("stroke-width", "0.5px")
})
//adds the .country attribute to allow for later updates on the svg element
.attr("class", "country")
}, [geoJson]);
return (
<div className="bg-blue-400">
<svg ref={svgRef} viewBox={`0 0 ${props.size[0]} ${props.size[1]}`} />
</div>
)
}
export default TestMap
When I use the unsimplifed json file it works fine. Each country is highlighted, but the strokewidth redraw takes a while. When all I do is change the fetch in WorldMapAtlas to the simplified json, an outline appears (which does not seems to be specific to one path, only disappears when all paths elements are deleted (dev tools)), and only the last feature in the json gets highlighted no matter where the cursor is.
The red dot is where my cursor is.
I've spent a lot of time on this, and I appreciate any help I could get! Thank you
After trying out a lot of different simplify options I feel silly. All you have to do is add the gj2008 option to the output command.
gj2008 [GeoJSON] use original GeoJSON spec (not RFC 7946)
This fixed all the problems! Although I have no idea why, I'm not sure how to check the original GeoJSON spec, and I'm not sure what the differences are. Hope this helps anyone who ran into the same problem!
So I'm building an app that allows you to chose more than one photo, on chose I set the files in React State then i listed for change for that state with useEffect so I can iterate and convert to base64 using FileRead to preview what I've uploaded. But I'm having a problem that the data I'm getting is weird, some of the files are read and added to the React State and some just appear just as File, here is the screenshot:
Here is the screenshot of the Console Log (can't add the object because is to long)
And here is how I add to the state the files on upload:
<input
className={styles.hidden_input}
type='file'
multiple='multiple'
accept='image/*'
onChange={(event) => {
const files = event.target.files;
if (files) setImages(files);
else setImages(null);
}}
And here is how I convert them when uploads are made:
useEffect(() => {
if (images.length !== 0) {
for (let i = 0; i < images.length; i++) {
let file = images[i];
const reader = new FileReader();
reader.onload = () => {
const single = reader.result;
setImagesStream([...images, single]);
};
reader.readAsDataURL(file);
}
} else {
console.log('No images where found.');
}
}, [images]);
When I try to iterate, just the last image shows the other show blank, because aren't converted.
You need to pass a function to the setState of 'setImagesStream' so it can read from the current value in the updater instead of reading the current value rendered. Here is the docs reference
Something like this should work:
setImagesStream((state) => {
return [...state, single]
});
I'm letting users upload a cover image.
I'm using ant design image upload. So it provides UI and the ability to crop images by default.
onst [fileList, setFileList] = useState([]);
const onPreview = async (file) => {
let src = file.url;
if (!src) {
src = await new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file.originFileObj);
reader.onload = () => resolve(reader.result);
});
}
const image = new Image();
image.src = src;
const imgWindow = window.open(src);
imgWindow.document.write(image.outerHTML);
};
return (
<div>
<UploadButton
fileList={fileList}
beforeUpload={() => false}
onChange={onChange}
onPreview={onPreview} aspect={2} listType="picture" />
<div/>
)
Once the cropping is done, before reaching the server, I want to give users the ability to preview the imageso they can confirm it before upload.
On successfully, uploading the image, I accessed the thumb URL to show users. It's blurry and distorted.
So I went on check the network section and I found the blob as shown in Image, which gives me a quality image that I can show in the product.
I'm new to programming. I was wondering, how to access it.
You can create a temporary image url out of a blob object.
const imageUrl = URL.createObjectURL(imageBlob) // imageBlob is of type blob
How can we pass file data along with other key-value pairs in the JSON payload in ReactJs?
I have a form where one input is of file type and others are simple text type input boxes. I have to pass the file and other input values to the restful post API in the JSON format from the frontend.
Could you provide a bit more context for this question?
A shot in the dark is you could use:
const ImportFromFileBodyComponent = () => {
let fileReader;
const handleFileRead = (e) => {
const content = fileReader.result;
console.log(content)
// … do something with the 'content' …
};
const handleFileChosen = (file) => {
fileReader = new FileReader();
fileReader.onloadend = handleFileRead;
fileReader.readAsText(file);
};
return <div className='upload-expense'>
<input
type='file'
id='file'
className='input-file'
accept='.csv'
onChange={e => handleFileChosen(e.target.files[0])}
/>
</div>;
};
This might provide you the contents of the file and you can use those in the application you desire.