diff --git a/src/components/CoordResolverInput.vue b/src/components/CoordResolverInput.vue new file mode 100644 index 0000000..eae2312 --- /dev/null +++ b/src/components/CoordResolverInput.vue @@ -0,0 +1,65 @@ + + + \ No newline at end of file diff --git a/src/store/app.ts b/src/store/app.ts index 8174f43..e199547 100644 --- a/src/store/app.ts +++ b/src/store/app.ts @@ -1,5 +1,7 @@ // Utilities import { defineStore } from 'pinia' +import axios, { AxiosResponse } from 'axios' +import {parseSimbadResponse, parseNEDResponse} from '@/utils/parseSimbadNEDResponse' export const useAppStore = defineStore('app', { state: () => ({ @@ -77,3 +79,84 @@ export const useAppStore = defineStore('app', { } }) + + +// Split store in logical component. `search` store will contain all data related to search form/process. +export const useSearchStore = defineStore('search', { + state: () => ({ + resolverName: '', + resolverServer: 'simbad', + resolverStatus: null, + resolverMessage: '', + resolverCoords: null, + resolverIsLoading: false, + }), + actions: { + resolveName() { + + let url: string + + // Resolve button clicked without providing the target name + if (this.resolverName == '') { + this.resolverStatus = 'error' + this.resolverMessage = 'Target Name is required' + this.resolverCoords = null + this.resolverIsLoading = false + return + } + + // select server resolver + switch (this.resolverServer) { + case 'simbad': + url = `https://simbad.cds.unistra.fr/simbad/sim-id?Ident=${encodeURIComponent(this.resolverName)}&output.format=votable&output.params=main_id,coo(d)` + break + case 'ned': + // NED via Sesame CDS https://vizier.cds.unistra.fr/vizier/doc/sesame.htx + // NED directly does not allow due to CORS + url = `https://cds.unistra.fr/cgi-bin/nph-sesame/-ox/~N?${encodeURIComponent(this.resolverName)}` + break + default: + throw new Error('Unknown name resolver') + } + + // set loading status while response is going on + this.resolverIsLoading = true + + let res: {status: string, message: string, payload: any} + + axios + .get(url) + .then((response) => { + switch (this.resolverServer) { + case 'simbad': + res = parseSimbadResponse(response.data) + break + case 'ned': + res = parseNEDResponse(response.data) + break + } + // successful server response, but status can be `error` if cannot find object in DB + this.resolverStatus = res.status + this.resolverMessage = res.message + this.resolverCoords = res.payload + this.resolverIsLoading = false + }) + .catch((error) => { + // error response is something went wrong + this.resolverStatus = 'error' + this.resolverMessage = `"${this.resolverServer}" cannot successfully resolve "${this.resolverName}" to coordinates` + this.resolverCoords = null + this.resolverIsLoading = false + // error details will be posted in console + console.warn(error); + }) + }, + // cleanup Target Resolver input + resetResolver() { + this.resolverStatus = null, + this.resolverMessage = '', + this.resolverCoords = null, + this.resolverIsLoading = false + } + } +}) \ No newline at end of file diff --git a/src/utils/parseSimbadNEDResponse.ts b/src/utils/parseSimbadNEDResponse.ts new file mode 100644 index 0000000..5a0ced7 --- /dev/null +++ b/src/utils/parseSimbadNEDResponse.ts @@ -0,0 +1,76 @@ +interface ResponsePayload { + status?: string + message: string + payload: any +} + + +export function parseSimbadResponse(data: any): ResponsePayload { + + // Parse the XML string into an XMLDocument + const parser = new DOMParser() + const xmlDoc = parser.parseFromString(data, 'text/xml') + + // Find the INFO element within the XML document suggesting a resolution error + const infoElement = xmlDoc.querySelector('INFO') + + if (infoElement) { + // Get error information from the INFO element + const errorInfo = infoElement.getAttribute('value') + console.warn(errorInfo) + return { status: 'error', message: `Simbad: ${errorInfo}`, payload: null } + + } else { + + // Find the relevant elements within the XML document + const tableData = xmlDoc.querySelector('TABLEDATA') + const rowData = tableData.querySelector('TR') + const columns = rowData.querySelectorAll('TD') + + // Extract the data from the columns + const name: string = columns[0].textContent?.trim() || '' // Main identifier + const RA_d: number = parseFloat(columns[1].textContent || '') // Right ascension + const DEC_d: number = parseFloat(columns[2].textContent || '') // Declination + + // Construct the output object + const output: { name: string, ra: number, dec: number } = { name: name, ra: RA_d, dec: DEC_d } + + return {status: 'success', message: 'Resolved successfully', payload: output } + } +} + + +export function parseNEDResponse(data: any): ResponsePayload { + + // Parse the XML string into an XMLDocument + const parser = new DOMParser() + const xmlDoc = parser.parseFromString(data, 'text/xml') + + // Get the Resolver element + const resolverElement = xmlDoc.querySelector('Resolver'); + + // Find the INFO element within the XML document suggesting a resolution error + const infoElement = xmlDoc.querySelector('INFO') + + if (infoElement) { + // Get error information from the INFO element + const errorInfo = infoElement.textContent + console.warn(errorInfo) + return { status: 'error', message: `NED: ${errorInfo}`, payload: null } + + } else { + // Extract the desired fields + const jra = resolverElement.querySelector('jradeg') + const jde = resolverElement.querySelector('jdedeg') + + const name: string = resolverElement.querySelector('oname').textContent + const jradeg: number = parseFloat(jra.textContent || '') + const jdedeg: number = parseFloat(jde.textContent || '') + + // Construct the output object + const output: { name: string, ra: number, dec: number } = { name: name, ra: jradeg, dec: jdedeg } + + return { status: 'success', message: 'Resolved successfully', payload: output } + } + +} diff --git a/src/views/Search.vue b/src/views/Search.vue index d6acc8c..55101ca 100644 --- a/src/views/Search.vue +++ b/src/views/Search.vue @@ -1,9 +1,14 @@