Skip to content

Commit

Permalink
Merge pull request #1 from archesproject/add_initial_map
Browse files Browse the repository at this point in the history
Add initial map to SearchPage component
  • Loading branch information
apeters authored Nov 23, 2024
2 parents 58d7fa8 + 817273f commit f5d0e99
Show file tree
Hide file tree
Showing 27 changed files with 1,984 additions and 25 deletions.
6 changes: 3 additions & 3 deletions afrc/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
if platform.system().lower() == "windows":
os.environ.setdefault("FORKED_BY_MULTIPROCESSING", "1")

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'afrc.settings')
app = Celery('afrc')
app.config_from_object('django.conf:settings', namespace='CELERY')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "afrc.settings")
app = Celery("afrc")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()
64 changes: 61 additions & 3 deletions afrc/src/afrc/Search/SearchPage.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
<script setup lang="ts">
import { onMounted, ref, watch } from "vue";
import type { Ref } from "vue";
import { useGettext } from "vue3-gettext";
import Toast from "primevue/toast";
import { useToast } from "primevue/usetoast";
import Button from "primevue/button";
import {
DEFAULT_ERROR_TOAST_LIFE,
ERROR,
} from "@/afrc/Search/constants.ts";
import SimpleSearchFilter from "@/afrc/Search/components/SimpleSearchFilter.vue";
import SearchResultItem from "@/afrc/Search/components/SearchResultItem.vue";
import MapView from "@/afrc/Search/components/MapView.vue";
import InteractiveMap from "@/afrc/Search/components/InteractiveMap/InteractiveMap.vue";
import { fetchMapData } from "@/afrc/Search/api.ts";
import type { GenericObject } from "@/afrc/Search/types";
import type {
Basemap,
MapLayer,
MapSource,
} from "@/afrc/Search/types.ts";
let query = getQueryObject(null);
let queryString = ref(JSON.stringify(query));
let searchResults = ref([]);
let resultsCount = ref("calculating...");
const showMap = ref(false);
const basemaps: Ref<Basemap[]> = ref([]);
const overlays: Ref<MapLayer[]> = ref([]);
const sources: Ref<MapSource[]> = ref([]);
const dataLoaded = ref(false);
const toast = useToast();
const { $gettext } = useGettext();
watch(queryString, () => {
doQuery();
Expand Down Expand Up @@ -118,8 +137,38 @@ const doQuery = function () {
// });
};
onMounted(() => {
async function fetchSystemMapData() {
try {
const mapData = await fetchMapData();
const layers = mapData.map_layers;
// omit search results layer for now
overlays.value = layers.filter(
(layer: MapLayer) =>
layer.isoverlay &&
layer.maplayerid !== "6b9d3c6a-60a4-4630-b4f8-4c5159b68cec",
);
layers.filter((layer: MapLayer) => !layer.isoverlay).forEach((layer: MapLayer) => {
basemaps.value.push({name: layer.name, active: layer.addtomap, value: layer.name, id: layer.name});
});
sources.value = mapData.map_sources;
} catch (error) {
toast.add({
severity: ERROR,
life: DEFAULT_ERROR_TOAST_LIFE,
summary: $gettext("Unable to fetch map data."),
detail: error instanceof Error ? error.message : undefined,
});
}
}
onMounted(async () =>{
doQuery();
await fetchSystemMapData();
dataLoaded.value = true;
});
</script>

Expand Down Expand Up @@ -163,7 +212,16 @@ onMounted(() => {
</div>
</section>

<MapView v-if="showMap" />
<div
v-if="showMap && dataLoaded"
style="width: 100%; height: 100%"
>
<InteractiveMap
:basemaps="basemaps"
:overlays="overlays"
:sources="sources"
/>
</div>

<aside v-if="!showMap">
<div>Search Facets</div>
Expand Down
66 changes: 66 additions & 0 deletions afrc/src/afrc/Search/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import arches from "arches";
import Cookies from "js-cookie";

import type { FeatureCollection } from "geojson";

function getToken() {
const token = Cookies.get("csrftoken");
if (!token) {
throw new Error("Missing csrftoken");
}
return token;
}

export const fetchGeoJSONBounds = async (features: FeatureCollection) => {
const response = await fetch(arches.urls["api-geojson-bounds"], {
method: "POST",
headers: { "X-CSRFTOKEN": getToken() },
body: JSON.stringify(features),
});
try {
const responseJson = await response.json();
if (response.ok) {
return responseJson;
}
throw new Error(responseJson.message);
} catch (error) {
throw new Error((error as Error).message || response.statusText);
}
};

export const fetchDrawnFeaturesBuffer = async (features: FeatureCollection) => {
const response = await fetch(arches.urls["api-feature-buffer"], {
method: "POST",
headers: { "X-CSRFTOKEN": getToken() },
body: JSON.stringify({ features }),
});
try {
const responseJson = await response.json();
if (response.ok) {
return responseJson;
}
throw new Error(responseJson.message);
} catch (error) {
throw new Error((error as Error).message || response.statusText);
}
};

export const createRequest = (url: string) => {
return async () => {
const response = await fetch(url);
try {
const responseJson = await response.json();
if (response.ok) {
return responseJson;
}
throw new Error(responseJson.message);
} catch (error) {
throw new Error((error as Error).message || response.statusText);
}
};
};

export const fetchSettings = createRequest(arches.urls["api-settings"]);
export const fetchMapData = createRequest(arches.urls["api-map-data"]);


141 changes: 141 additions & 0 deletions afrc/src/afrc/Search/components/InteractiveMap/InteractiveMap.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<script setup lang="ts">
import { onMounted, provide, ref, watch } from "vue";
import { useGettext } from "vue3-gettext";
import { useToast } from "primevue/usetoast";
import MapComponent from "@/afrc/Search/components/InteractiveMap/components/MapComponent.vue";
import MapFilter from "@/afrc/Search/components/InteractiveMap/components/MapFilter/MapFilter.vue";
import InteractionsDrawer from "@/afrc/Search/components/InteractiveMap/components/InteractionsDrawer.vue";
import OverlayControls from "@/afrc/Search/components/InteractiveMap/components/OverlayControls.vue";
import BasemapControls from "@/afrc/Search/components/InteractiveMap/components/BasemapControls.vue";
import { fetchSettings } from "@/afrc/Search/api.ts";
import {
DEFAULT_ERROR_TOAST_LIFE,
ERROR,
} from "@/afrc/Search/constants.ts";
import type { Ref } from "vue";
import type { Feature, Map } from "maplibre-gl";
import type {
Basemap,
MapInteractionItem,
MapLayer,
MapSource,
Settings,
} from "@/afrc/Search/types.ts";
const toast = useToast();
const { $gettext } = useGettext();
const props = defineProps<{
overlays: MapLayer[];
basemaps: Basemap[];
sources: MapSource[];
}>();
const map: Ref<Map | null> = ref(null);
const settings: Ref<Settings | null> = ref(null);
const mapInteractionItems: MapInteractionItem[] = [
{
name: "Foo",
header: "Map Foo",
component: MapFilter,
icon: "pi pi-star",
},
{
name: "Bar",
header: "Map Bar",
component: OverlayControls,
icon: "pi pi-hashtag",
},
{
name: "Baz",
header: "Map Baz",
component: BasemapControls,
icon: "pi pi-globe",
},
];
const basemap: Ref<Basemap | null> = ref(null);
const selectedDrawnFeature: Ref<Feature | null> = ref(null);
const emits = defineEmits(["drawnFeatureSelected", "drawnFeaturesUpdated"]);
console.log(props);
provide("overlays", props.overlays);
provide("basemaps", props.basemaps);
provide("selectedDrawnFeature", selectedDrawnFeature);
watch(
() => props.basemaps,
(updatedBasemaps) => {
for (let updatedBasemap of updatedBasemaps) {
if (updatedBasemap.active) {
basemap.value = updatedBasemap;
break;
}
}
},
{ deep: true, immediate: true },
);
onMounted(async () => {
await fetchSystemSettings();
});
async function fetchSystemSettings() {
try {
console.log("Fetching settings");
settings.value = await fetchSettings();
} catch (error) {
toast.add({
severity: ERROR,
life: DEFAULT_ERROR_TOAST_LIFE,
summary: $gettext("Unable to fetch settings."),
detail: error instanceof Error ? error.message : undefined,
});
}
}
function updateSelectedDrawnFeature(feature: Feature) {
selectedDrawnFeature.value = feature;
emits("drawnFeatureSelected", feature);
}
</script>

<template>
<div
v-if="settings"
style="display: flex; height: 100%; width: 100%"
>
<MapComponent
:settings="settings"
:basemap="basemap"
:overlays="overlays"
:sources="sources"
:is-drawing-enabled="true"
@map-initialized="
(mapInstance) => {
map = mapInstance;
}
"
@drawn-features-updated="
(features) => {
emits('drawnFeaturesUpdated', features);
}
"
@drawn-feature-selected="updateSelectedDrawnFeature"
/>
<InteractionsDrawer
v-if="map"
:map="map"
:settings="settings"
:items="mapInteractionItems"
/>
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<script setup lang="ts">
import { inject, onMounted, ref, watch } from "vue";
import RadioButton from "primevue/radiobutton";
import type { Map } from "maplibre-gl";
import type { PropType } from "vue";
import type { Basemap } from "@/afrc/Search/types.ts";
defineProps({
map: {
type: Object as PropType<Map>,
required: true,
},
settings: {
type: Object as PropType<Record<string, unknown> | null>,
default: null,
},
});
const basemaps: Basemap[] = inject("basemaps", []);
const selectedBasemap = ref<Basemap | null>(null);
watch(selectedBasemap, (newBasemap) => {
basemaps.forEach((basemap) => {
basemap.active = basemap === newBasemap;
});
});
onMounted(() => {
const activeBasemap = basemaps.find((basemap) => basemap.active);
if (activeBasemap) {
selectedBasemap.value = activeBasemap;
}
});
</script>

<template>
<div
v-for="basemap in basemaps"
:key="basemap.id"
>
<RadioButton
v-model="selectedBasemap"
:value="basemap"
:label="basemap.name"
/>
<label>{{ basemap.name }}</label>
</div>
</template>
Loading

0 comments on commit f5d0e99

Please sign in to comment.