Call JPL HORIZONS website to obtain orbital elements based on the
        provided targetname, epochs, and center code. For valid center
        codes, please refer to http://ssd.jpl.nasa.gov/horizons.cgi

        :param center:  str;
           center body (default: 500@10 = Sun)
        :result: int; number of epochs queried
        :example: >>> ceres = callhorizons.query('Ceres')
                  >>> ceres.set_epochrange('2016-02-23 00:00', '2016-02-24 00:00', '1h')
                  >>> print (ceres.get_elements(), 'epochs queried')

        The queried properties and their definitions are:
           +------------------+-----------------------------------------------+
           | Property         | Definition                                    |
           +==================+===============================================+
           | targetname       | official number, name, designation [string]   |
           +------------------+-----------------------------------------------+
           | H                | absolute magnitude in V band (float, mag)     |
           +------------------+-----------------------------------------------+
           | G                | photometric slope parameter (float)           |
           +------------------+-----------------------------------------------+
           | datetime_jd      | epoch Julian Date (float)                     |
           +------------------+-----------------------------------------------+
           | e                | eccentricity (float)                          |
           +------------------+-----------------------------------------------+
           | p                | periapsis distance (float, au)                |
           +------------------+-----------------------------------------------+
           | a                | semi-major axis (float, au)                   |
           +------------------+-----------------------------------------------+
           | incl             | inclination (float, deg)                      |
           +------------------+-----------------------------------------------+
           | node             | longitude of Asc. Node (float, deg)           |
           +------------------+-----------------------------------------------+
           | argper           | argument of the perifocus (float, deg)        |
           +------------------+-----------------------------------------------+
           | Tp               | time of periapsis (float, Julian Date)        |
           +------------------+-----------------------------------------------+
           | meananomaly      | mean anomaly (float, deg)                     |
           +------------------+-----------------------------------------------+
           | trueanomaly      | true anomaly (float, deg)                     |
           +------------------+-----------------------------------------------+
           | period           | orbital period (float, Earth yr)              |
           +------------------+-----------------------------------------------+
           | Q                | apoapsis distance (float, au)                 |
           +------------------+-----------------------------------------------+

def get_elements(self, center='500@10', asteroid=False, comet=False):
        """Call JPL HORIZONS website to obtain orbital elements based on the
        provided targetname, epochs, and center code. For valid center
        codes, please refer to http://ssd.jpl.nasa.gov/horizons.cgi

        :param center:  str;
           center body (default: 500@10 = Sun)
        :result: int; number of epochs queried
        :example: >>> ceres = callhorizons.query('Ceres')
                  >>> ceres.set_epochrange('2016-02-23 00:00', '2016-02-24 00:00', '1h')
                  >>> print (ceres.get_elements(), 'epochs queried')

        The queried properties and their definitions are:
           +------------------+-----------------------------------------------+
           | Property         | Definition                                    |
           +==================+===============================================+
           | targetname       | official number, name, designation [string]   |
           +------------------+-----------------------------------------------+
           | H                | absolute magnitude in V band (float, mag)     |
           +------------------+-----------------------------------------------+
           | G                | photometric slope parameter (float)           |
           +------------------+-----------------------------------------------+
           | datetime_jd      | epoch Julian Date (float)                     |
           +------------------+-----------------------------------------------+
           | e                | eccentricity (float)                          |
           +------------------+-----------------------------------------------+
           | p                | periapsis distance (float, au)                |
           +------------------+-----------------------------------------------+
           | a                | semi-major axis (float, au)                   |
           +------------------+-----------------------------------------------+
           | incl             | inclination (float, deg)                      |
           +------------------+-----------------------------------------------+
           | node             | longitude of Asc. Node (float, deg)           |
           +------------------+-----------------------------------------------+
           | argper           | argument of the perifocus (float, deg)        |
           +------------------+-----------------------------------------------+
           | Tp               | time of periapsis (float, Julian Date)        |
           +------------------+-----------------------------------------------+
           | meananomaly      | mean anomaly (float, deg)                     |
           +------------------+-----------------------------------------------+
           | trueanomaly      | true anomaly (float, deg)                     |
           +------------------+-----------------------------------------------+
           | period           | orbital period (float, Earth yr)              |
           +------------------+-----------------------------------------------+
           | Q                | apoapsis distance (float, au)                 |
           +------------------+-----------------------------------------------+
        """

        # encode objectname for use in URL
        objectname = urllib.quote(self.targetname.encode("utf8"))

        # call Horizons website and extract data
        url = "https://ssd.jpl.nasa.gov/horizons_batch.cgi?batch=l" \
            + "&TABLE_TYPE='ELEMENTS'" \
            + "&CSV_FORMAT='YES'" \
              + "&CENTER='" + str(center) + "'" \
              + "&OUT_UNITS='AU-D'" \
              + "&REF_PLANE='ECLIPTIC'" \
              + "REF_SYSTEM='J2000'" \
              + "&TP_TYPE='ABSOLUTE'" \
              + "&ELEM_LABELS='YES'" \
              + "CSV_FORMAT='YES'" \
              + "&OBJ_DATA='YES'"

        # check if self.targetname is a designation
        # lower case + upper case + numbers = pot. case sensitive designation
        if self.not_smallbody:
            url += "&COMMAND='" + \
                   urllib.quote(self.targetname.encode("utf8")) + "'"
        elif self.isorbit_record():
            # Comet orbit record. Do not use DES, CAP. This test must
            # occur before asteroid test.
            url += "&COMMAND='" + \
                   urllib.quote(self.targetname.encode("utf8")) + "%3B'"
        elif self.isasteroid() and not self.comet:
            # for asteroids, use 'DES="designation";'
            for ident in self.parse_asteroid():
                if ident is not None:
                    break
            if ident is None:
                ident = self.targetname
            url += "&COMMAND='" + \
                   urllib.quote(str(ident).encode("utf8")) + "%3B'"
        elif self.iscomet() and not self.asteroid:
            # for comets, potentially append the current apparition
            # (CAP) parameter, or the fragmentation flag (NOFRAG)
            for ident in self.parse_comet():
                if ident is not None:
                    break
            if ident is None:
                ident = self.targetname
            url += "&COMMAND='DES=" + \
                   urllib.quote(ident.encode("utf8")) + "%3B" + \
                   ("NOFRAG%3B" if self.nofrag else "") + \
                   ("CAP'" if self.cap else "'")
        # elif (not self.targetname.replace(' ', '').isalpha() and not
        #      self.targetname.isdigit() and not
        #      self.targetname.islower() and not
        #      self.targetname.isupper()):
        #     url += "&COMMAND='DES=" + str(objectname) + "%3B'"
        else:
            url += "&COMMAND='" + str(objectname) + "%3B'"

        if self.discreteepochs is not None:
            url += "&TLIST="
            for date in self.discreteepochs:
                url += "'" + str(date) + "'"
        elif (self.start_epoch is not None and self.stop_epoch is not None and
              self.step_size is not None):
            url += "&START_TIME='" \
                + urllib.quote(self.start_epoch.encode("utf8")) + "'" \
                + "&STOP_TIME='" \
                + urllib.quote(self.stop_epoch.encode("utf8")) + "'" \
                + "&STEP_SIZE='" + str(self.step_size) + "'"
        else:
            raise IOError('no epoch information given')

        self.url = url

        i = 0  # count number of connection tries
        while True:
            try:
                src = urllib.urlopen(url).readlines()
                break
            except urllib.URLError:
                time.sleep(0.1)
                # in case the HORIZONS website is blocked (due to another query)
                # wait 1 second and try again
            i += 1
            if i > 50:
                return 0  # website could not be reached

        # disseminate website source code
        # identify header line and extract data block (elements data)
        # also extract targetname, abs. magnitude (H), and slope parameter (G)
        headerline = []
        datablock = []
        in_datablock = False
        H, G = np.nan, np.nan
        for idx, line in enumerate(src):
            line = line.decode('UTF-8')

            if 'JDTDB,' in line:
                headerline = line.split(',')
            if "$$EOE\n" in line:
                in_datablock = False
            if in_datablock:
                datablock.append(line)
            if "$$SOE\n" in line:
                in_datablock = True
            if "Target body name" in line:
                targetname = line[18:50].strip()
            if "rotational period in hours)" in src[idx].decode('UTF-8'):
                HGline = src[idx+2].decode('UTF-8').split('=')
                if 'B-V' in HGline[2] and 'G' in HGline[1]:
                    try:
                        H = float(HGline[1].rstrip('G'))
                    except ValueError:
                        pass
                    try:
                        G = float(HGline[2].rstrip('B-V'))
                    except ValueError:
                        pass
            if ("Multiple major-bodies match string" in src[idx].decode('UTF-8') or
                ("Matching small-bodies" in src[idx].decode('UTF-8') and not
                    "No matches found" in src[idx+1].decode('UTF-8'))):
                raise ValueError('Ambiguous target name; check URL: %s' %
                                 url)
            if ("Matching small-bodies" in src[idx].decode('UTF-8') and
                    "No matches found" in src[idx+1].decode('UTF-8')):
                raise ValueError('Unknown target; check URL: %s' % url)

        # field identification for each line in
        elements = []
        for line in datablock:
            line = line.split(',')

            this_el = []
            fieldnames = []
            datatypes = []

            # create a dictionary for each date (each line)
            for idx, item in enumerate(headerline):
                if (item.find('JDTDB') > -1):
                    this_el.append(np.float64(line[idx]))
                    fieldnames.append('datetime_jd')
                    datatypes.append(np.float64)
                if (item.find('EC') > -1):
                    this_el.append(np.float64(line[idx]))
                    fieldnames.append('e')
                    datatypes.append(np.float64)
                if (item.find('QR') > -1):
                    this_el.append(np.float64(line[idx]))
                    fieldnames.append('p')
                    datatypes.append(np.float64)
                if (item.find('A') > -1) and len(item.strip()) == 1:
                    this_el.append(np.float64(line[idx]))
                    fieldnames.append('a')
                    datatypes.append(np.float64)
                if (item.find('IN') > -1):
                    this_el.append(np.float64(line[idx]))
                    fieldnames.append('incl')
                    datatypes.append(np.float64)
                if (item.find('OM') > -1):
                    this_el.append(np.float64(line[idx]))
                    fieldnames.append('node')
                    datatypes.append(np.float64)
                if (item.find('W') > -1):
                    this_el.append(np.float64(line[idx]))
                    fieldnames.append('argper')
                    datatypes.append(np.float64)
                if (item.find('Tp') > -1):
                    this_el.append(np.float64(line[idx]))
                    fieldnames.append('Tp')
                    datatypes.append(np.float64)
                if (item.find('MA') > -1):
                    this_el.append(np.float64(line[idx]))
                    fieldnames.append('meananomaly')
                    datatypes.append(np.float64)
                if (item.find('TA') > -1):
                    this_el.append(np.float64(line[idx]))
                    fieldnames.append('trueanomaly')
                    datatypes.append(np.float64)
                if (item.find('PR') > -1):
                    # Earth years
                    this_el.append(np.float64(line[idx])/(365.256))
                    fieldnames.append('period')
                    datatypes.append(np.float64)
                if (item.find('AD') > -1):
                    this_el.append(np.float64(line[idx]))
                    fieldnames.append('Q')
                    datatypes.append(np.float64)

            # append targetname
            this_el.append(targetname)
            fieldnames.append('targetname')
            datatypes.append(object)

            # append H
            this_el.append(H)
            fieldnames.append('H')
            datatypes.append(np.float64)

            # append G
            this_el.append(G)
            fieldnames.append('G')
            datatypes.append(np.float64)

            if len(this_el) > 0:
                elements.append(tuple(this_el))

        if len(elements) == 0:
            return 0

        # combine elements with column names and data types into ndarray
        assert len(elements[0]) == len(fieldnames) == len(datatypes)
        self.data = np.array(elements,
                             dtype=[(str(fieldnames[i]), datatypes[i]) for i
                                    in range(len(fieldnames))])

        return len(self)