From 57eee7bb5dc509027f0b19c540cdb7a9841dbcaf Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Sat, 27 Jul 2024 12:46:50 -0400 Subject: [PATCH] chore: Add GeoJSON endpoint to retrieve combined GeoJSON data from static files --- backend/server/worldtravel/urls.py | 3 +- backend/server/worldtravel/views.py | 40 ++++++++++++++++++++++- frontend/src/routes/map/+page.server.ts | 23 ++------------ frontend/src/routes/map/+page.svelte | 42 +++++++++++++++++++++++-- 4 files changed, 83 insertions(+), 25 deletions(-) diff --git a/backend/server/worldtravel/urls.py b/backend/server/worldtravel/urls.py index 8f0610f..0a9a6c5 100644 --- a/backend/server/worldtravel/urls.py +++ b/backend/server/worldtravel/urls.py @@ -2,7 +2,7 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter -from .views import CountryViewSet, RegionViewSet, VisitedRegionViewSet, regions_by_country, visits_by_country +from .views import CountryViewSet, RegionViewSet, VisitedRegionViewSet, regions_by_country, visits_by_country, GeoJSONView router = DefaultRouter() router.register(r'countries', CountryViewSet, basename='countries') @@ -13,4 +13,5 @@ urlpatterns = [ path('', include(router.urls)), path('/regions/', regions_by_country, name='regions-by-country'), path('/visits/', visits_by_country, name='visits-by-country'), + path('geojson/', GeoJSONView.as_view({'get': 'list'}), name='geojson'), ] diff --git a/backend/server/worldtravel/views.py b/backend/server/worldtravel/views.py index 4bcec73..0880f0a 100644 --- a/backend/server/worldtravel/views.py +++ b/backend/server/worldtravel/views.py @@ -6,6 +6,10 @@ from rest_framework.permissions import IsAuthenticated from django.shortcuts import get_object_or_404 from rest_framework.response import Response from rest_framework.decorators import api_view, permission_classes +import os +import json +from django.conf import settings +from django.contrib.staticfiles import finders @api_view(['GET']) @permission_classes([IsAuthenticated]) @@ -49,4 +53,38 @@ class VisitedRegionViewSet(viewsets.ModelViewSet): serializer.is_valid(raise_exception=True) self.perform_create(serializer) headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) \ No newline at end of file + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + +class GeoJSONView(viewsets.ViewSet): + """ + Combine all GeoJSON data from .json files in static/data into a single GeoJSON object. + """ + def list(self, request): + combined_geojson = { + "type": "FeatureCollection", + "features": [] + } + + # Use Django's static file finder to locate the 'data' directory + data_dir = finders.find('data') + + if not data_dir or not os.path.isdir(data_dir): + return Response({"error": "Data directory does not exist."}, status=404) + + for filename in os.listdir(data_dir): + if filename.endswith('.json'): + file_path = os.path.join(data_dir, filename) + try: + with open(file_path, 'r') as f: + json_data = json.load(f) + # Check if the JSON data is GeoJSON + if isinstance(json_data, dict) and "type" in json_data: + if json_data["type"] == "FeatureCollection": + combined_geojson["features"].extend(json_data.get("features", [])) + elif json_data["type"] == "Feature": + combined_geojson["features"].append(json_data) + # You can add more conditions here for other GeoJSON types if needed + except (IOError, json.JSONDecodeError) as e: + return Response({"error": f"Error reading file {filename}: {str(e)}"}, status=500) + + return Response(combined_geojson) \ No newline at end of file diff --git a/frontend/src/routes/map/+page.server.ts b/frontend/src/routes/map/+page.server.ts index bdbfb43..e5a8368 100644 --- a/frontend/src/routes/map/+page.server.ts +++ b/frontend/src/routes/map/+page.server.ts @@ -5,12 +5,6 @@ import type { Adventure, VisitedRegion } from '$lib/types'; const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000'; export const load = (async (event) => { - let countryCodesToFetch = ['FR', 'US', 'CA', 'DE', 'AU', 'MX', 'JP']; - let geoJSON = { - type: 'FeatureCollection', - features: [] - }; - if (!event.locals.user) { return redirect(302, '/login'); } else { @@ -20,6 +14,8 @@ export const load = (async (event) => { } }); + let geoJsonUrl = `${endpoint}/api/geojson/` as string; + let visitedRegionsFetch = await fetch(`${endpoint}/api/visitedregion/`, { headers: { Cookie: `${event.cookies.get('auth')}` @@ -27,19 +23,6 @@ export const load = (async (event) => { }); let visitedRegions = (await visitedRegionsFetch.json()) as VisitedRegion[]; - await Promise.all( - countryCodesToFetch.map(async (code) => { - let res = await fetch(`${endpoint}/static/data/${code.toLowerCase()}.json`); - console.log('fetching ' + code); - let json = await res.json(); - if (!json) { - console.error(`Failed to fetch ${code} GeoJSON`); - } else { - geoJSON.features = geoJSON.features.concat(json.features); - } - }) - ); - if (!visitedFetch.ok) { console.error('Failed to fetch visited adventures'); return redirect(302, '/login'); @@ -61,7 +44,7 @@ export const load = (async (event) => { return { props: { markers, - geoJSON, + geoJsonUrl, visitedRegions } }; diff --git a/frontend/src/routes/map/+page.svelte b/frontend/src/routes/map/+page.svelte index 259e54a..c425b0b 100644 --- a/frontend/src/routes/map/+page.svelte +++ b/frontend/src/routes/map/+page.svelte @@ -17,6 +17,24 @@ let clickedName = ''; + let showVisited = true; + let showPlanned = true; + + $: { + if (!showVisited) { + markers = markers.filter((marker) => marker.type !== 'visited'); + } else { + const visitedMarkers = data.props.markers.filter((marker) => marker.type === 'visited'); + markers = [...markers, ...visitedMarkers]; + } + if (!showPlanned) { + markers = markers.filter((marker) => marker.type !== 'planned'); + } else { + const plannedMarkers = data.props.markers.filter((marker) => marker.type === 'planned'); + markers = [...markers, ...plannedMarkers]; + } + } + let newMarker = []; let newLongitude = null; @@ -61,7 +79,7 @@ let visitedRegions = data.props.visitedRegions; - let geoJSON = data.props.geoJSON; + let geoJSON = []; let visitArray = []; @@ -77,11 +95,29 @@ } // mapped to the checkbox - let showGEO = true; + let showGEO = false; + $: { + if (showGEO && geoJSON.length === 0) { + (async () => { + geoJSON = await fetch(data.props.geoJsonUrl).then((res) => res.json()); + })(); + } else if (!showGEO) { + geoJSON = []; + } + } let createModalOpen = false; + + + {#if newMarker.length > 0} +