Spaces:
Runtime error
Runtime error
Merge pull request #7 from DebasishDhal/feat/wiki-geosearch
Browse files- click on a location (with exploration mode on), a number of wikipages which are close to the clicked location will pop up
- added doc string to backend routes
- Both a small and significant merge. It has the first core functionality that I had wanted this project to have.
Next target
- Add auto zoom, i.e. after every click, a no. of markers will pop up, i want the map to zoom to an adequate level so that the markers would be properly usable (and not just visible).
- frontend/src/components/Map.js +252 -3
- main.py +105 -2
frontend/src/components/Map.js
CHANGED
@@ -82,6 +82,12 @@ const Map = ( { onMapClick, searchQuery, contentType, setSearchQuery, setSubmitt
|
|
82 |
|
83 |
const [countryBorders, setCountryBorders] = useState(null);
|
84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
const CenterMap = ({ position }) => {
|
86 |
const map = useMap();
|
87 |
useEffect(() => {
|
@@ -208,7 +214,46 @@ const Map = ( { onMapClick, searchQuery, contentType, setSearchQuery, setSubmitt
|
|
208 |
};
|
209 |
|
210 |
const handleMapClick = useCallback(async (lat, lon) => {
|
211 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
212 |
const updatedPoints = [...geoPoints, { lat, lon }];
|
213 |
if (updatedPoints.length > 2) {
|
214 |
updatedPoints.shift(); // keep only two
|
@@ -249,7 +294,7 @@ const Map = ( { onMapClick, searchQuery, contentType, setSearchQuery, setSubmitt
|
|
249 |
console.log("Invalid tool mode:", geoToolMode);
|
250 |
}
|
251 |
|
252 |
-
}, [geoToolMode, geoPoints, geoUnit, areaPoints]);
|
253 |
|
254 |
useEffect(() => {
|
255 |
if (geoPoints.length === 2) {
|
@@ -655,6 +700,41 @@ const Map = ( { onMapClick, searchQuery, contentType, setSearchQuery, setSubmitt
|
|
655 |
</Marker>
|
656 |
)}
|
657 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
658 |
{/* Only show geodistance markers/polyline if sidebar is open */}
|
659 |
{geoSidebarOpen && geoToolMode === "distance" && geoPoints.map((pt, index) => (
|
660 |
<Marker key={`geo-${index}`}
|
@@ -799,7 +879,176 @@ const Map = ( { onMapClick, searchQuery, contentType, setSearchQuery, setSubmitt
|
|
799 |
</button>
|
800 |
)}
|
801 |
|
802 |
-
{/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
803 |
{geoSidebarOpen && (
|
804 |
<div style={{
|
805 |
position: 'fixed',
|
|
|
82 |
|
83 |
const [countryBorders, setCountryBorders] = useState(null);
|
84 |
|
85 |
+
const [explorationMode, setExplorationMode] = useState(false);
|
86 |
+
const [explorationRadius, setExplorationRadius] = useState(10000);
|
87 |
+
const [explorationLimit, setExplorationLimit] = useState(10);
|
88 |
+
const [explorationMarkers, setExplorationMarkers] = useState([]);
|
89 |
+
const [explorationSidebarOpen, setExplorationSidebarOpen] = useState(false);
|
90 |
+
|
91 |
const CenterMap = ({ position }) => {
|
92 |
const map = useMap();
|
93 |
useEffect(() => {
|
|
|
214 |
};
|
215 |
|
216 |
const handleMapClick = useCallback(async (lat, lon) => {
|
217 |
+
if (explorationMode) {
|
218 |
+
// Handle exploration mode click
|
219 |
+
try {
|
220 |
+
const res = await fetch(`${BACKEND_URL}/wiki/nearby`, {
|
221 |
+
method: 'POST',
|
222 |
+
headers: { 'Content-Type': 'application/json' },
|
223 |
+
body: JSON.stringify({
|
224 |
+
lat: lat,
|
225 |
+
lon: lon,
|
226 |
+
radius: explorationRadius,
|
227 |
+
limit: explorationLimit
|
228 |
+
}),
|
229 |
+
});
|
230 |
+
|
231 |
+
if (res.ok) {
|
232 |
+
const data = await res.json();
|
233 |
+
const markers = data.pages.map(page => ({
|
234 |
+
position: [page.lat, page.lon],
|
235 |
+
title: page.title,
|
236 |
+
distance: page.dist
|
237 |
+
}));
|
238 |
+
// setExplorationMarkers(markers);
|
239 |
+
// Now adding the main clicked point
|
240 |
+
setExplorationMarkers([
|
241 |
+
{
|
242 |
+
position: [lat, lon],
|
243 |
+
title: 'Clicked Location',
|
244 |
+
distance: 0,
|
245 |
+
isClickedPoint: true
|
246 |
+
},
|
247 |
+
...markers
|
248 |
+
]);
|
249 |
+
console.log(`Found ${markers.length} nearby pages`);
|
250 |
+
} else {
|
251 |
+
console.error('Failed to fetch nearby pages');
|
252 |
+
}
|
253 |
+
} catch (err) {
|
254 |
+
console.error('Error fetching nearby pages:', err);
|
255 |
+
}
|
256 |
+
} else if (geoToolMode === "distance") {
|
257 |
const updatedPoints = [...geoPoints, { lat, lon }];
|
258 |
if (updatedPoints.length > 2) {
|
259 |
updatedPoints.shift(); // keep only two
|
|
|
294 |
console.log("Invalid tool mode:", geoToolMode);
|
295 |
}
|
296 |
|
297 |
+
}, [explorationMode, explorationRadius, explorationLimit, geoToolMode, geoPoints, geoUnit, areaPoints]);
|
298 |
|
299 |
useEffect(() => {
|
300 |
if (geoPoints.length === 2) {
|
|
|
700 |
</Marker>
|
701 |
)}
|
702 |
|
703 |
+
{/* Exploration Mode Markers */}
|
704 |
+
{explorationMode && explorationMarkers.map((marker, index) => (
|
705 |
+
<Marker
|
706 |
+
key={`exploration-${index}`}
|
707 |
+
position={marker.position}
|
708 |
+
icon={marker.isClickedPoint ? new L.Icon({
|
709 |
+
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png',
|
710 |
+
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
|
711 |
+
iconSize: [25, 41],
|
712 |
+
iconAnchor: [12, 41],
|
713 |
+
popupAnchor: [1, -34],
|
714 |
+
shadowSize: [41, 41]
|
715 |
+
}) : new L.Icon({
|
716 |
+
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-green.png',
|
717 |
+
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
|
718 |
+
iconSize: [25, 41],
|
719 |
+
iconAnchor: [12, 41],
|
720 |
+
popupAnchor: [1, -34],
|
721 |
+
shadowSize: [41, 41]
|
722 |
+
})}
|
723 |
+
>
|
724 |
+
<Popup>
|
725 |
+
<div>
|
726 |
+
<strong>{marker.title}</strong><br />
|
727 |
+
{!marker.isClickedPoint && (
|
728 |
+
<small>Distance: {marker.distance.toFixed(1)}m</small>
|
729 |
+
)}
|
730 |
+
{marker.isClickedPoint && (
|
731 |
+
<small>Pos: {marker.position[0].toFixed(4)}, {marker.position[1].toFixed(4)}</small>
|
732 |
+
)}
|
733 |
+
</div>
|
734 |
+
</Popup>
|
735 |
+
</Marker>
|
736 |
+
))}
|
737 |
+
|
738 |
{/* Only show geodistance markers/polyline if sidebar is open */}
|
739 |
{geoSidebarOpen && geoToolMode === "distance" && geoPoints.map((pt, index) => (
|
740 |
<Marker key={`geo-${index}`}
|
|
|
879 |
</button>
|
880 |
)}
|
881 |
|
882 |
+
{/* Exploration Mode Button */}
|
883 |
+
{!explorationSidebarOpen && !geoSidebarOpen && (
|
884 |
+
<button
|
885 |
+
onClick={() => setExplorationSidebarOpen(true)}
|
886 |
+
style={{
|
887 |
+
position: 'absolute',
|
888 |
+
top: 50, // Position below Geo Tools button
|
889 |
+
right: 12,
|
890 |
+
zIndex: 1000,
|
891 |
+
padding: '6px 12px',
|
892 |
+
backgroundColor: '#4caf50',
|
893 |
+
color: 'white',
|
894 |
+
border: 'none',
|
895 |
+
borderRadius: 4,
|
896 |
+
cursor: 'pointer'
|
897 |
+
}}
|
898 |
+
>
|
899 |
+
Exploration
|
900 |
+
</button>
|
901 |
+
)}
|
902 |
+
|
903 |
+
{/* Exploration Mode Button - when Geo Tools sidebar is open */}
|
904 |
+
{!explorationSidebarOpen && geoSidebarOpen && (
|
905 |
+
<button
|
906 |
+
onClick={() => setExplorationSidebarOpen(true)}
|
907 |
+
style={{
|
908 |
+
position: 'fixed',
|
909 |
+
top: 320, // Position below Geo Tools sidebar
|
910 |
+
right: 24,
|
911 |
+
zIndex: 2000,
|
912 |
+
padding: '6px 12px',
|
913 |
+
backgroundColor: '#4caf50',
|
914 |
+
color: 'white',
|
915 |
+
border: 'none',
|
916 |
+
borderRadius: 4,
|
917 |
+
cursor: 'pointer'
|
918 |
+
}}
|
919 |
+
>
|
920 |
+
Exploration
|
921 |
+
</button>
|
922 |
+
)}
|
923 |
+
|
924 |
+
{/* Exploration Sidebar */}
|
925 |
+
{explorationSidebarOpen && (
|
926 |
+
<div style={{
|
927 |
+
position: 'fixed',
|
928 |
+
top: geoSidebarOpen ? 320 : 24, // Position below Geo Tools sidebar if open
|
929 |
+
right: 24,
|
930 |
+
width: 280,
|
931 |
+
background: 'white',
|
932 |
+
borderRadius: 10,
|
933 |
+
boxShadow: '0 2px 12px rgba(0,0,0,0.12)',
|
934 |
+
zIndex: 2000,
|
935 |
+
padding: 20,
|
936 |
+
display: 'flex',
|
937 |
+
flexDirection: 'column',
|
938 |
+
gap: 16,
|
939 |
+
border: '1px solid #eee'
|
940 |
+
}}>
|
941 |
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
942 |
+
<strong>Exploration Mode</strong>
|
943 |
+
<button
|
944 |
+
onClick={() => {
|
945 |
+
setExplorationSidebarOpen(false);
|
946 |
+
setExplorationMode(false);
|
947 |
+
setExplorationMarkers([]);
|
948 |
+
}}
|
949 |
+
style={{
|
950 |
+
background: 'none',
|
951 |
+
border: 'none',
|
952 |
+
fontSize: 18,
|
953 |
+
cursor: 'pointer',
|
954 |
+
color: '#888'
|
955 |
+
}}
|
956 |
+
title="Close"
|
957 |
+
>×</button>
|
958 |
+
</div>
|
959 |
+
|
960 |
+
<div>
|
961 |
+
<label style={{ fontWeight: 500, marginBottom: 8, display: 'block' }}>
|
962 |
+
Search Radius (meters):
|
963 |
+
</label>
|
964 |
+
<input
|
965 |
+
type="range"
|
966 |
+
min="10"
|
967 |
+
max="10000"
|
968 |
+
step="1000"
|
969 |
+
value={explorationRadius}
|
970 |
+
onChange={(e) => setExplorationRadius(parseInt(e.target.value))}
|
971 |
+
style={{ width: '100%' }}
|
972 |
+
/>
|
973 |
+
<div style={{ textAlign: 'center', marginTop: 4 }}>
|
974 |
+
{explorationRadius.toLocaleString()}m
|
975 |
+
</div>
|
976 |
+
</div>
|
977 |
+
|
978 |
+
<div>
|
979 |
+
<label style={{ fontWeight: 500, marginBottom: 8, display: 'block' }}>
|
980 |
+
Number of Results:
|
981 |
+
</label>
|
982 |
+
<input
|
983 |
+
type="range"
|
984 |
+
min="1"
|
985 |
+
max="50"
|
986 |
+
step="1"
|
987 |
+
value={explorationLimit}
|
988 |
+
onChange={(e) => setExplorationLimit(parseInt(e.target.value))}
|
989 |
+
style={{ width: '100%' }}
|
990 |
+
/>
|
991 |
+
<div style={{ textAlign: 'center', marginTop: 4 }}>
|
992 |
+
{explorationLimit} results
|
993 |
+
</div>
|
994 |
+
</div>
|
995 |
+
|
996 |
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
997 |
+
<input
|
998 |
+
type="checkbox"
|
999 |
+
id="explorationMode"
|
1000 |
+
checked={explorationMode}
|
1001 |
+
onChange={(e) => setExplorationMode(e.target.checked)}
|
1002 |
+
/>
|
1003 |
+
<label htmlFor="explorationMode" style={{ fontWeight: 500 }}>
|
1004 |
+
Enable Exploration Mode
|
1005 |
+
</label>
|
1006 |
+
</div>
|
1007 |
+
|
1008 |
+
{explorationMode && (
|
1009 |
+
<div style={{
|
1010 |
+
padding: '8px 12px',
|
1011 |
+
backgroundColor: '#e8f5e8',
|
1012 |
+
borderRadius: 4,
|
1013 |
+
fontSize: '14px',
|
1014 |
+
color: '#2e7d32'
|
1015 |
+
}}>
|
1016 |
+
✓ Click anywhere on the map to find nearby Wikipedia pages
|
1017 |
+
</div>
|
1018 |
+
)}
|
1019 |
+
|
1020 |
+
{explorationMarkers.length > 0 && (
|
1021 |
+
<div style={{
|
1022 |
+
padding: '8px 12px',
|
1023 |
+
backgroundColor: '#e3f2fd',
|
1024 |
+
borderRadius: 4,
|
1025 |
+
fontSize: '14px',
|
1026 |
+
color: '#1976d2'
|
1027 |
+
}}>
|
1028 |
+
Found {explorationMarkers.length} nearby pages
|
1029 |
+
</div>
|
1030 |
+
)}
|
1031 |
+
|
1032 |
+
<button
|
1033 |
+
onClick={() => {
|
1034 |
+
setExplorationMarkers([]);
|
1035 |
+
}}
|
1036 |
+
style={{
|
1037 |
+
padding: '6px 0',
|
1038 |
+
borderRadius: 4,
|
1039 |
+
border: '1px solid #f44336',
|
1040 |
+
background: '#f44336',
|
1041 |
+
color: 'white',
|
1042 |
+
fontWeight: 500,
|
1043 |
+
cursor: 'pointer'
|
1044 |
+
}}
|
1045 |
+
>
|
1046 |
+
Clear Markers
|
1047 |
+
</button>
|
1048 |
+
</div>
|
1049 |
+
)}
|
1050 |
+
|
1051 |
+
{/* Geo Sidebar - Keep as is */}
|
1052 |
{geoSidebarOpen && (
|
1053 |
<div style={{
|
1054 |
position: 'fixed',
|
main.py
CHANGED
@@ -22,6 +22,12 @@ class Geodistance(BaseModel):
|
|
22 |
lon2: float = Field(..., ge=-180, le=180)
|
23 |
unit: str = "km"
|
24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
app.add_middleware(
|
26 |
CORSMiddleware,
|
27 |
allow_origins=["*"], # Replace with your frontend domain in prod
|
@@ -40,6 +46,12 @@ def health_check():
|
|
40 |
|
41 |
@app.get("/wiki/search/summary/{summary_page_name}")
|
42 |
async def get_wiki_summary(summary_page_name: str, background_tasks: BackgroundTasks):
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
if summary_page_name in summary_cache:
|
44 |
# print("Cache hit for summary:", page_name) #Working
|
45 |
return JSONResponse(content=summary_cache[summary_page_name], status_code=200)
|
@@ -77,7 +89,13 @@ async def get_wiki_summary(summary_page_name: str, background_tasks: BackgroundT
|
|
77 |
)
|
78 |
|
79 |
@app.get("/wiki/search/full/{full_page}")
|
80 |
-
def search_wiki_full_page(full_page: str, background_tasks: BackgroundTasks):
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
if full_page in full_page_cache:
|
82 |
# print("Cache hit for full_page:", full_page) #Working
|
83 |
return JSONResponse(content=full_page_cache[full_page], status_code=200)
|
@@ -117,6 +135,10 @@ def search_wiki_full_page(full_page: str, background_tasks: BackgroundTasks):
|
|
117 |
|
118 |
@app.post("/geodistance")
|
119 |
def get_geodistance(payload: Geodistance):
|
|
|
|
|
|
|
|
|
120 |
lat1, lon1 = payload.lat1, payload.lon1
|
121 |
lat2, lon2 = payload.lat2, payload.lon2
|
122 |
unit = payload.unit
|
@@ -151,11 +173,92 @@ def get_geodistance(payload: Geodistance):
|
|
151 |
status_code=200
|
152 |
)
|
153 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
@app.get("/random")
|
155 |
def random():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
return JSONResponse(
|
157 |
content={
|
158 |
-
"
|
|
|
159 |
},
|
160 |
status_code=200
|
161 |
)
|
|
|
22 |
lon2: float = Field(..., ge=-180, le=180)
|
23 |
unit: str = "km"
|
24 |
|
25 |
+
class NearbyWikiPage(BaseModel):
|
26 |
+
lat: float = Field(default=54.163337, ge=-90, le=90)
|
27 |
+
lon: float = Field(default=37.561109, ge=-180, le=180)
|
28 |
+
radius: int = Field(default=1000, ge=10, le=10000,description="Distance in meters from the reference point")
|
29 |
+
limit: int = Field(10, ge=1, description="Number of pages to return")
|
30 |
+
|
31 |
app.add_middleware(
|
32 |
CORSMiddleware,
|
33 |
allow_origins=["*"], # Replace with your frontend domain in prod
|
|
|
46 |
|
47 |
@app.get("/wiki/search/summary/{summary_page_name}")
|
48 |
async def get_wiki_summary(summary_page_name: str, background_tasks: BackgroundTasks):
|
49 |
+
"""
|
50 |
+
This function fetches the summary of a Wikipedia page along with its geographical coordinates.
|
51 |
+
It also caches the result in ephemeral in-memory cache in the background.
|
52 |
+
Input: summary_page_name: str - Name of the Wikipedia page to fetch summary for.
|
53 |
+
Output: {"title": "Page Title", "content": "Summary content here", "latitude": float, "longitude": float9}
|
54 |
+
"""
|
55 |
if summary_page_name in summary_cache:
|
56 |
# print("Cache hit for summary:", page_name) #Working
|
57 |
return JSONResponse(content=summary_cache[summary_page_name], status_code=200)
|
|
|
89 |
)
|
90 |
|
91 |
@app.get("/wiki/search/full/{full_page}")
|
92 |
+
async def search_wiki_full_page(full_page: str, background_tasks: BackgroundTasks):
|
93 |
+
"""
|
94 |
+
This function fetches the full content of a Wikipedia page along with its geographical coordinates.
|
95 |
+
It also caches the result in ephemeral in-memory cache in the background.
|
96 |
+
Input: full_page: str - Name of the Wikipedia page to fetch full content for.
|
97 |
+
Output: {"title": "Page Title", "content": "Full content here", "latitude": float, "longitude": float}
|
98 |
+
"""
|
99 |
if full_page in full_page_cache:
|
100 |
# print("Cache hit for full_page:", full_page) #Working
|
101 |
return JSONResponse(content=full_page_cache[full_page], status_code=200)
|
|
|
135 |
|
136 |
@app.post("/geodistance")
|
137 |
def get_geodistance(payload: Geodistance):
|
138 |
+
"""
|
139 |
+
Input: "lat1", "lon1", "lat2", "lon2", "unit (km/mi)"
|
140 |
+
Output: {"distance": float, "unit": str, "lat1": float, "lon1": float, "lat2": float, "lon2": float}
|
141 |
+
"""
|
142 |
lat1, lon1 = payload.lat1, payload.lon1
|
143 |
lat2, lon2 = payload.lat2, payload.lon2
|
144 |
unit = payload.unit
|
|
|
173 |
status_code=200
|
174 |
)
|
175 |
|
176 |
+
@app.post("/wiki/nearby")
|
177 |
+
async def get_nearby_wiki_pages(payload: NearbyWikiPage):
|
178 |
+
"""
|
179 |
+
Returns a list of wikipedia pages whose geographical coordinates are within a specified radius from a given location.
|
180 |
+
Input:
|
181 |
+
- lat: Latitude of the reference point
|
182 |
+
- lon: Longitude of the reference point
|
183 |
+
- radius: Radius in meters within which to search for pages
|
184 |
+
- limit: Maximum number of pages to return
|
185 |
+
|
186 |
+
Output:
|
187 |
+
{
|
188 |
+
"pages": [
|
189 |
+
{
|
190 |
+
"pageid": 123456,
|
191 |
+
"title": "Page Title",
|
192 |
+
"lat": 54.163337,
|
193 |
+
"lon": 37.561109,
|
194 |
+
"dist": 123.45 # Dist. in meters from the reference point
|
195 |
+
...
|
196 |
+
},
|
197 |
+
...
|
198 |
+
],
|
199 |
+
"count": 10 #Total no. of such pages
|
200 |
+
}
|
201 |
+
"""
|
202 |
+
lat, lon = payload.lat, payload.lon
|
203 |
+
radius = payload.radius
|
204 |
+
limit = payload.limit
|
205 |
+
|
206 |
+
url = ("https://en.wikipedia.org/w/api.php"+"?action=query"
|
207 |
+
"&list=geosearch"
|
208 |
+
f"&gscoord={lat}|{lon}"
|
209 |
+
f"&gsradius={radius}"
|
210 |
+
f"&gslimit={limit}"
|
211 |
+
"&format=json")
|
212 |
+
print(url)
|
213 |
+
try:
|
214 |
+
response = requests.get(url, timeout=10)
|
215 |
+
if response.status_code != 200:
|
216 |
+
return JSONResponse(
|
217 |
+
content={"error": "Failed to fetch nearby pages"},
|
218 |
+
status_code=500
|
219 |
+
)
|
220 |
+
data = response.json()
|
221 |
+
|
222 |
+
pages = data.get("query", {}).get("geosearch", [])
|
223 |
+
|
224 |
+
return JSONResponse(
|
225 |
+
content={
|
226 |
+
"pages": pages,
|
227 |
+
"count": len(pages)
|
228 |
+
},
|
229 |
+
status_code=200
|
230 |
+
)
|
231 |
+
except Exception as e:
|
232 |
+
return JSONResponse(
|
233 |
+
content={"error": str(e)},
|
234 |
+
status_code=500
|
235 |
+
)
|
236 |
+
|
237 |
+
|
238 |
+
|
239 |
+
|
240 |
@app.get("/random")
|
241 |
def random():
|
242 |
+
url = "https://en.wikipedia.org/w/api.php?action=query&list=geosearch&gscoord=54.163337|37.561109&gsradius=10000&gslimit=10&format=json"
|
243 |
+
response = requests.get(url, timeout=10)
|
244 |
+
|
245 |
+
if response.status_code != 200:
|
246 |
+
return JSONResponse(
|
247 |
+
content={"error": "Failed to fetch random page"},
|
248 |
+
status_code=500
|
249 |
+
)
|
250 |
+
data = response.json()
|
251 |
+
pages = data.get("query", {}).get("geosearch", [])
|
252 |
+
if not pages:
|
253 |
+
return JSONResponse(
|
254 |
+
content={"error": "No pages found"},
|
255 |
+
status_code=404
|
256 |
+
)
|
257 |
+
|
258 |
return JSONResponse(
|
259 |
content={
|
260 |
+
"pages": pages,
|
261 |
+
"count": len(pages)
|
262 |
},
|
263 |
status_code=200
|
264 |
)
|