From 726f919609ad1765d94bb02b2436e0aab369f1d1 Mon Sep 17 00:00:00 2001 From: Tiny Paws Date: Tue, 23 Sep 2025 16:15:13 +0200 Subject: [PATCH 1/3] Aligning with OSM usage policies Added 24 hours cache on Nominatim requests Added rate limiting on Nominatim requests Added OpenStreetMap attribution on leaflet map --- web/src/components/LeafletMap.tsx | 1 + .../ActionButton/LocationSelector.tsx | 32 ++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/web/src/components/LeafletMap.tsx b/web/src/components/LeafletMap.tsx index e1facc54c..8d5f66a04 100644 --- a/web/src/components/LeafletMap.tsx +++ b/web/src/components/LeafletMap.tsx @@ -34,6 +34,7 @@ const LocationMarker = (props: MarkerProps) => { useEffect(() => { map.attributionControl.setPrefix(""); + map.attributionControl.addAttribution("OpenStreetMap"); map.locate(); }, []); diff --git a/web/src/components/MemoEditor/ActionButton/LocationSelector.tsx b/web/src/components/MemoEditor/ActionButton/LocationSelector.tsx index d101bb064..e7d015a3b 100644 --- a/web/src/components/MemoEditor/ActionButton/LocationSelector.tsx +++ b/web/src/components/MemoEditor/ActionButton/LocationSelector.tsx @@ -1,6 +1,6 @@ import { LatLng } from "leaflet"; import { MapPinIcon, XIcon } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import toast from "react-hot-toast"; import LeafletMap from "@/components/LeafletMap"; import { Button } from "@/components/ui/button"; @@ -21,6 +21,12 @@ interface State { position?: LatLng; } +interface NomatimRateLimit { + lastNominatimFetch: Date; + nominatimTimeoutId: number | undefined; + timeBetweenFetch: number; +} + const LocationSelector = (props: Props) => { const t = useTranslate(); const [state, setState] = useState({ @@ -28,6 +34,12 @@ const LocationSelector = (props: Props) => { placeholder: props.location?.placeholder || "", position: props.location ? new LatLng(props.location.latitude, props.location.longitude) : undefined, }); + const rateLimit = useRef({ + lastNominatimFetch: new Date(0), + nominatimTimeoutId: undefined, + timeBetweenFetch: 1300, + }); + const [popoverOpen, setPopoverOpen] = useState(false); useEffect(() => { @@ -63,14 +75,16 @@ const LocationSelector = (props: Props) => { } }, [popoverOpen]); - useEffect(() => { + const updateReverseGeocoding = () => { if (!state.position) { setState({ ...state, placeholder: "" }); return; } - // Fetch reverse geocoding data. - fetch(`https://nominatim.openstreetmap.org/reverse?lat=${state.position.lat}&lon=${state.position.lng}&format=json`) + fetch(`https://nominatim.openstreetmap.org/reverse?lat=${state.position.lat}&lon=${state.position.lng}&format=json`, { + cache: "default", + headers: new Headers({ "Cache-Control": "max-age=86400" }), + }) .then((response) => response.json()) .then((data) => { if (data && data.display_name) { @@ -81,6 +95,16 @@ const LocationSelector = (props: Props) => { toast.error("Failed to fetch reverse geocoding data"); console.error("Failed to fetch reverse geocoding data:", error); }); + } + + useEffect(() => { + // Fetch reverse geocoding with rate limits + clearTimeout(rateLimit.current.nominatimTimeoutId); + const timeLeft = rateLimit.current.timeBetweenFetch - (new Date().getTime() - rateLimit.current.lastNominatimFetch.getTime()); + rateLimit.current.nominatimTimeoutId = setTimeout(() => { + updateReverseGeocoding(); + rateLimit.current.lastNominatimFetch = new Date(); + }, timeLeft); }, [state.position]); const onPositionChanged = (position: LatLng) => { From 83ccc30554d84f52e97d7c31c90131790052735a Mon Sep 17 00:00:00 2001 From: Tiny Paws Date: Wed, 1 Oct 2025 20:31:44 +0200 Subject: [PATCH 2/3] Fixed typo and appeased linter --- .../components/MemoEditor/ActionButton/LocationSelector.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/components/MemoEditor/ActionButton/LocationSelector.tsx b/web/src/components/MemoEditor/ActionButton/LocationSelector.tsx index e7d015a3b..3cda7884d 100644 --- a/web/src/components/MemoEditor/ActionButton/LocationSelector.tsx +++ b/web/src/components/MemoEditor/ActionButton/LocationSelector.tsx @@ -21,7 +21,7 @@ interface State { position?: LatLng; } -interface NomatimRateLimit { +interface NominatimRateLimit { lastNominatimFetch: Date; nominatimTimeoutId: number | undefined; timeBetweenFetch: number; @@ -34,7 +34,7 @@ const LocationSelector = (props: Props) => { placeholder: props.location?.placeholder || "", position: props.location ? new LatLng(props.location.latitude, props.location.longitude) : undefined, }); - const rateLimit = useRef({ + const rateLimit = useRef({ lastNominatimFetch: new Date(0), nominatimTimeoutId: undefined, timeBetweenFetch: 1300, @@ -95,7 +95,7 @@ const LocationSelector = (props: Props) => { toast.error("Failed to fetch reverse geocoding data"); console.error("Failed to fetch reverse geocoding data:", error); }); - } + }; useEffect(() => { // Fetch reverse geocoding with rate limits From e3fe6ec6d96ae803bf1423020f01fdc69a603785 Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 9 Oct 2025 00:43:18 +0800 Subject: [PATCH 3/3] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Johnny --- web/src/components/MemoEditor/ActionButton/LocationSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/MemoEditor/ActionButton/LocationSelector.tsx b/web/src/components/MemoEditor/ActionButton/LocationSelector.tsx index 3cda7884d..3f5c0a47b 100644 --- a/web/src/components/MemoEditor/ActionButton/LocationSelector.tsx +++ b/web/src/components/MemoEditor/ActionButton/LocationSelector.tsx @@ -104,7 +104,7 @@ const LocationSelector = (props: Props) => { rateLimit.current.nominatimTimeoutId = setTimeout(() => { updateReverseGeocoding(); rateLimit.current.lastNominatimFetch = new Date(); - }, timeLeft); + }, Math.max(0, timeLeft)); }, [state.position]); const onPositionChanged = (position: LatLng) => {