Search code examples
pythonpandasdata-analysis

Generating/collecting URLs from "NEO Earth Close Approaches" database to link to Orbit Viewer


I'm working on a personal project utilizing data from the NEO Earth Close Approaches database. I'm cleaning/prepping the dataset with pandas to be used within a Tableau Viz, but I am very curious to hear how others would approach one particular issue.

When viewing the database in browser, the table includes the column "View CA", which links out to an Orbit Viewer showing each close approach. Downloading the CSV or Excel file does not provide these links. I would like to have these links in my dataframe.

I have figured out the format of the link through inspect element; for example, the View CA link for Object (2023 LD):

https://cneos.jpl.nasa.gov/ca/ov/#load=&orientation=0,0,0,1&lookat=Earth&interval=2&eclipticaxis=false&eclipticgrid=false&largeFont=false&distance=29919.57414&pitch=0&roll=0&yaw=0&scale=1.000&rotateX=-30.20870289631195&rotateY=38.134339235185024&desig=2023%20LD&cajd=2460104.544743703&

At the end of the link, you can see that ...desig=2023%20LD&cajd=2460104.544743703& uses the Object/designation (desig=2023%20LD... from 2023 LD) and cajd=... uses the object's close approach date in Julian Date format, which I have created a conversion function to generate.

Firstly, is my outlined solution a sane way to go about this? Secondly, how would you approach this challenge yourself? My main curiosity as I stated is whether there are other, possibly simpler ways to do this as I took way too much time to figure out my solution where it currently is.

Thanks!

So far my solution has been to use regex functions and the cajd conversion function to generate the links based on the data available from the downloadable format.


Solution

  • Looking at the source code of the page, the URLs are constructed on the fly via JavaScript. The Js function that constructs the links looks like this (you can find it through the Web Developer Tools):

        $scope.data.onRenderView = function (desig, jd, dist) {
          // console.log('DEBUG: enter onRenderView()...'); // TESTING
          //
          // Set the viewer scale based on the approach distance:
          var scale = 0.1;
          if      ( dist < 0.02 ) { scale = 1.0; }
          else if ( dist < 0.05 ) { scale = 0.5; }
          else if ( dist < 0.1  ) { scale = 0.2; }
          //
          // Set the orbit-viewer query-parameters string:
          var ov_par = '';
          ov_par += '#load=&orientation=0,0,0,1';
          // ov_par += '&viewfrom=above';
          ov_par += '&lookat=Earth';
          ov_par += '&interval=2';
          ov_par += '&eclipticaxis=false';
          ov_par += '&eclipticgrid=false';
          ov_par += '&largeFont=' + isSafari;
          ov_par += '&distance=29919.57414';
          ov_par += '&pitch=0&roll=0&yaw=0';
          ov_par += '&scale=' + scale.toFixed(3);
          ov_par += '&rotateX=-30.20870289631195';
          ov_par += '&rotateY=38.134339235185024';
          ov_par += '&desig=DESIG';
          ov_par += '&cajd=JD&';
          ov_par = '/ca/ov/' + encodeURI(ov_par);
          ov_par = ov_par.replace('DESIG', desig);
          ov_par = ov_par.replace('JD', jd);
          $sce.trustAsResourceUrl(ov_par);
          //
          // Remove any prior viewer then add the new viewer to the DOM:
          $('#orbit-viewer-iframe').remove();
          var iframe = '<iframe id="orbit-viewer-iframe" style="width: 1000px; height: 600px;" src="';
          iframe += ov_par;
          iframe += '"></iframe>';
          $('#orbit-viewer-container').append(iframe);
          //
          // Set a flag to show the new viewer:
          $scope.data.orbitViewerShow = true;
        };
    

    So to get all links you need 3 parameters desig, jd, dist. The page gets them via Ajax call. So to construct the links you can use next example:

    import urllib
    import requests
    import pandas as pd
    
    
    def get_link(desig, jd, dist):
        scale = 0.1
        if dist < 0.02:
            scale = 1.0
        elif dist < 0.05:
            scale = 0.5
        elif dist < 0.1:
            scale = 0.2
    
        ov_par = ''
        ov_par += '#load=&orientation=0,0,0,1'
        # ov_par += '&viewfrom=above'
        ov_par += '&lookat=Earth'
        ov_par += '&interval=2'
        ov_par += '&eclipticaxis=false'
        ov_par += '&eclipticgrid=false'
        ov_par += '&largeFont=false' #+ str(isSafari)
        ov_par += '&distance=29919.57414'
        ov_par += '&pitch=0&roll=0&yaw=0'
        ov_par += '&scale=' + format(scale, '.3f')
        ov_par += '&rotateX=-30.20870289631195'
        ov_par += '&rotateY=38.134339235185024'
        ov_par += '&desig=DESIG'
        ov_par += '&cajd=JD&'
        ov_par = '/ca/ov/' + ov_par
        ov_par = ov_par.replace('DESIG', urllib.parse.quote(desig))
        ov_par = ov_par.replace('JD', str(jd))
    
        return 'https://cneos.jpl.nasa.gov' + ov_par
    
    api_url= 'https://ssd-api.jpl.nasa.gov/cad.api?diameter=1&dist-max=0.05&fullname=1&nea-comet=1&rating=1&www=1'
    data = requests.get(api_url).json()
    
    df = pd.DataFrame(data['data'], columns=data['fields'])
    
    df['jd'] = pd.to_numeric(df['jd'])
    df['dist'] = pd.to_numeric(df['dist'])
    
    for des, jd, dist in zip(df['des'], df['jd'], df['dist']):
        print(get_link(des.split('|')[-1], jd, dist))
    

    Prints:

    https://cneos.jpl.nasa.gov/ca/ov/#load=&orientation=0,0,0,1&lookat=Earth&interval=2&eclipticaxis=false&eclipticgrid=false&largeFont=false&distance=29919.57414&pitch=0&roll=0&yaw=0&scale=1.000&rotateX=-30.20870289631195&rotateY=38.134339235185024&desig=2023%20LD&cajd=2460104.544743703&
    https://cneos.jpl.nasa.gov/ca/ov/#load=&orientation=0,0,0,1&lookat=Earth&interval=2&eclipticaxis=false&eclipticgrid=false&largeFont=false&distance=29919.57414&pitch=0&roll=0&yaw=0&scale=0.500&rotateX=-30.20870289631195&rotateY=38.134339235185024&desig=2023%20JB3&cajd=2460105.360445169&
    https://cneos.jpl.nasa.gov/ca/ov/#load=&orientation=0,0,0,1&lookat=Earth&interval=2&eclipticaxis=false&eclipticgrid=false&largeFont=false&distance=29919.57414&pitch=0&roll=0&yaw=0&scale=1.000&rotateX=-30.20870289631195&rotateY=38.134339235185024&desig=2023%20LA&cajd=2460107.524286449&
    https://cneos.jpl.nasa.gov/ca/ov/#load=&orientation=0,0,0,1&lookat=Earth&interval=2&eclipticaxis=false&eclipticgrid=false&largeFont=false&distance=29919.57414&pitch=0&roll=0&yaw=0&scale=0.500&rotateX=-30.20870289631195&rotateY=38.134339235185024&desig=488453&cajd=2460107.536744452&
    
    ...
    

    The df looks like this:

                       des orbit_id                 jd                 cd                 dist              dist_min             dist_max             v_rel              v_inf t_sigma_f       h diameter diameter_sigma             fullname  rating
    0     bK23L00D|2023 LD        2  2460104.544743703  2023-Jun-09 01:04  0.00635081714223801    0.0063401113338132  0.00636152260566893  9.10079205473851   9.05457441910954   < 00:01  26.371     None           None            (2023 LD)       0
    1    bK23J03B|2023 JB3       13  2460105.360445169  2023-Jun-09 20:39   0.0362163376263994    0.0361880148384531   0.0362446602808189  6.94939728103983   6.93880250289472   < 00:01  24.174     None           None           (2023 JB3)       0
    2     bK23L00A|2023 LA        3  2460107.524286449  2023-Jun-12 00:35   0.0044093539762013   0.00439127221463214  0.00442743413650688  10.4456580761954   10.3876472288082   < 00:01  25.208     None           None            (2023 LA)       1
    3      a0488453|488453       93  2460107.536744452  2023-Jun-12 00:53   0.0211423059659372    0.0211421868604232   0.0211424250714549  21.4724651802065   21.4665951888416   < 00:01   19.28     None           None     488453 (1994 XD)       2
    4    bK22W04N|2022 WN4       21  2460108.897696208  2023-Jun-13 09:33   0.0276624541212163    0.0275584911929838    0.027766703533987  15.0756063294164   15.0692157693588     00:08   21.76     None           None           (2022 WN4)       1
    
    ...