pycrs/parse.py
def _from_wkt(string, wkttype=None, strict=False):
    """
    Internal method for parsing wkt, with minor differences depending on ogc or esri style.

    Arguments:

    - *string*: The OGC or ESRI WKT representation as a string.
    - *wkttype* (optional): How to parse the WKT string, as either 'ogc', 'esri', or None. If None, tries to autodetect the wkt type before parsing (default). 
    - *strict* (optional): When True, the parser is strict about names having to match
        exactly with upper and lowercases. Default is not strict (False).

    Returns:

    - A CS instance of the indicated type. 
    """
    # TODO
    # - Make function for finding next elemt by name, instead of knowing its arg index position
    # - Maybe verify elem arg name

    # make sure valid wkttype
    if wkttype: wkttype = wkttype.lower()
    assert wkttype in ("ogc","esri",None)
    
    # remove newlines and multi spaces
    string = " ".join(string.split())
    
    # parse arguments into components
    def _consume_bracket(chars, char):
        "char must be the opening bracket"
        consumed = ""
        depth = 1
        while char and depth > 0:
            consumed += char
            char = next(chars, None)
            # update depth level
            if char == "[":
                depth += 1
            elif char == "]":
                depth -= 1
        consumed += char # consume the last closing char too
        return consumed
    
    def _consume_quote(chars, char, quotechar):
        "char and quotechar must be the opening quote char"
        consumed = ""
        # consume the first opening char
        consumed += char 
        char = next(chars, None)
        # consume inside
        while char and char != quotechar:
            consumed += char
            char = next(chars, None)
        # consume the last closing char too
        consumed += char 
        return consumed
    
    def _next_elem(chars, char):
        "char must be the first char of the text that precedes brackets"
        header = ""
        # skip until next header
        while not char.isalpha():
            char = next(chars, None)
        # first consume the element text header
        while char.isalpha():
            header += char
            char = next(chars, None)
        # skip until next brackets (in case of spaces)
        while char != "[":
            char = next(chars, None)
        # then consume the element bracket contents
        if char == "[":
            content = _consume_bracket(chars, char)
        char = next(chars, None)
        # split content into args list
        content = content[1:-1] # remove enclosing brackets
        content = _split_except(content)
        # recursively load all subelems
        for i,item in enumerate(content):
            if isinstance(item, str) and "[" in item:
                chars = (char for char in item)
                char = next(chars)
                item = _next_elem(chars, char)
                content[i] = item
        return header, content
    
    def _clean_value(string):
        string = string.strip()
        try: string = float(string)
        except: pass
        return string
    
    def _split_except(string):
        "split the string on every comma, except not while inside quotes or square brackets"
        chars = (char for char in string)
        char = next(chars)
        items = []
        consumed = ""
        while char:
            # dont split on quotes, just consume it
            if char in ("'", '"'):
                consumed += _consume_quote(chars, char, char)
            # dont split inside brackets, just consume it
            elif char == "[":
                consumed += _consume_bracket(chars, char)
            # new splitchar found, add what has been consumed so far as an item, reset, and start consuming until next splitchar
            elif char == ",":
                consumed = _clean_value(consumed)
                items.append(consumed)
                consumed = ""
            # consume normal char
            elif char:
                consumed += char
            # next
            char = next(chars, None)
        # append last item too
        consumed = _clean_value(consumed)
        items.append(consumed)
        return items
    
    # load into nested tuples and arglists
    crstuples = []
    chars = (char for char in string)
    char = next(chars)
    while char:
        header,content = _next_elem(chars, char)
        crstuples.append((header, content))
        char = next(chars, None)

    # autodetect wkttype if not specified
    if not wkttype:
        topheader,topcontent = crstuples[0]
        if topheader == "PROJCS":
            geogcsheader,geogcscontent = topcontent[1]
        elif topheader == "GEOGCS":
            geogcsheader,geogcscontent = topheader,topcontent

        # datum elem should be second under geogcs
        datumheader, datumcontent = geogcscontent[1]
        datumname = datumcontent[0].upper().strip('"')
        
        # esri wkt datums all use "D_" before the datum name
        if datumname.startswith("D_"):
            wkttype = "esri"
        else:
            wkttype = "ogc"

    # parse into actual crs objects
    def _parse_top(header, content):
        "procedure for parsing the toplevel crs element and all its children"
        if header.upper() == "PROJCS":
            
            # find name
            csname = content[0].strip('"')
            
            # find geogcs elem (by running parse again)
            subheader, subcontent = content[1]
            geogcs = _parse_top(subheader, subcontent)
            
            # find projection elem
            for part in content:
                if isinstance(part, tuple):
                    subheader,subcontent = part
                    if subheader == "PROJECTION":
                        break
            projname = subcontent[0].strip('"')
            projclass = projections.find(projname, "%s_wkt" % wkttype, strict)
            if projclass:
                proj = projclass()
            else:
                raise NotImplementedError("Unsupported projection: The specified projection name %r could not be found in the list of supported projections" % projname)
            
            # find params
            params = []
            for part in content:
                if isinstance(part, tuple):
                    subheader,subcontent = part
                    if subheader == "PARAMETER":
                        name, value = subcontent[0].strip('"'), subcontent[1]
                        itemclass = parameters.find(name, "%s_wkt" % wkttype, strict)
                        if itemclass:
                            item = itemclass(value)
                            params.append(item)
                            
            # find unit
            for part in content:
                if isinstance(part, tuple):
                    subheader,subcontent = part
                    if subheader == "UNIT":
                        break
            unitname,value = subcontent[0].strip('"'), subcontent[1]
            unitclass = units.find(unitname, "%s_wkt" % wkttype, strict)
            if unitclass:
                unit = unitclass()
            else:
                unit = units.Unknown()

            unit.unitmultiplier.value = value # override default multiplier
            linunit = unit
            
            # find twin axis maybe
##            if len(content) >= 6:
##                twinax = (parameters.Axis(
##            else:
##                twinax = None
            
            # put it all together
            projcs = containers.ProjCS(csname, geogcs, proj, params, linunit) #, twinax)
            return projcs

        elif header.upper() == "GEOGCS":
            # name
            csname = content[0].strip('"')
            
            # datum
            subheader, subcontent = content[1]
            
            ## datum name
            datumname = subcontent[0].strip('"')
            datumclass = datums.find(datumname, "%s_wkt" % wkttype, strict)
            if datumclass:
                datum = datumclass()
            else:
                datum = datums.Unknown()
                
            ## datum ellipsoid
            subsubheader, subsubcontent = subcontent[1]
            ellipsname = subsubcontent[0].strip('"')
            ellipsclass = ellipsoids.find(ellipsname, "%s_wkt" % wkttype, strict)
            if ellipsclass:
                ellipsoid = ellipsclass()
            else:
                ellipsoid = ellipsoids.Unknown()

            ellipsoid.semimaj_ax = parameters.SemiMajorRadius(subsubcontent[1])
            if subsubcontent[2] == 0:
                # WKT falsely sets inverse flattening to 0 for spheroids
                # but actually it cannot be 0, it is the flattening that is 0
                ellipsoid.flat = parameters.Flattening(subsubcontent[2])
            else:
                ellipsoid.inv_flat = parameters.InverseFlattening(subsubcontent[2])

            ## datum shift
            if wkttype == "ogc":
                for subsubheader,subsubcontent in subcontent[1:]:
                    if subsubheader == "TOWGS84":
                        datumshift = parameters.DatumShift(subsubcontent)
                        break
                else:
                    datumshift = None
            elif wkttype == "esri":
                # not used in esri wkt
                datumshift = None
                
            ## put it all togehter
            datum.ellips = ellipsoid
            datum.datumshift = datumshift
            
            # prime mer
            subheader, subcontent = content[2]
            prime_mer = parameters.PrimeMeridian(subcontent[1])
            
            # angunit
            subheader, subcontent = content[3]
            unitname,value = subcontent[0].strip('"'), subcontent[1]
            unitclass = units.find(unitname, "%s_wkt" % wkttype, strict)
            if unitclass:
                unit = unitclass()
            else:
                unit = units.Unknown()
            unit.unitmultiplier.value = value # override default multiplier
            angunit = unit
            
            # twin axis
            # ...
            
            # put it all together
            geogcs = containers.GeogCS(csname, datum, prime_mer, angunit, twin_ax=None)
            return geogcs

    # toplevel collection
    header, content = crstuples[0]
    crs = _parse_top(header, content)
        
    # use args to create crs
    return crs
def _from_wkt(string, wkttype=None, strict=False):
    """
    Internal method for parsing wkt, with minor differences depending on ogc or esri style.

    Arguments:

    - *string*: The OGC or ESRI WKT representation as a string.
    - *wkttype* (optional): How to parse the WKT string, as either 'ogc', 'esri', or None. If None, tries to autodetect the wkt type before parsing (default). 
    - *strict* (optional): When True, the parser is strict about names having to match
        exactly with upper and lowercases. Default is not strict (False).

    Returns:

    - A CS instance of the indicated type. 
    """
    # TODO
    # - Make function for finding next elemt by name, instead of knowing its arg index position
    # - Maybe verify elem arg name

    # make sure valid wkttype
    if wkttype: wkttype = wkttype.lower()
    assert wkttype in ("ogc","esri",None)
    
    # remove newlines and multi spaces
    string = " ".join(string.split())
    
    # parse arguments into components
    def _consume_bracket(chars, char):
        "char must be the opening bracket"
        consumed = ""
        depth = 1
        while char and depth > 0:
            consumed += char
            char = next(chars, None)
            # update depth level
            if char == "[":
                depth += 1
            elif char == "]":
                depth -= 1
        consumed += char # consume the last closing char too
        return consumed
    
    def _consume_quote(chars, char, quotechar):
        "char and quotechar must be the opening quote char"
        consumed = ""
        # consume the first opening char
        consumed += char 
        char = next(chars, None)
        # consume inside
        while char and char != quotechar:
            consumed += char
            char = next(chars, None)
        # consume the last closing char too
        consumed += char 
        return consumed
    
    def _next_elem(chars, char):
        "char must be the first char of the text that precedes brackets"
        header = ""
        # skip until next header
        while not char.isalpha():
            char = next(chars, None)
        # first consume the element text header
        while char.isalpha():
            header += char
            char = next(chars, None)
        # skip until next brackets (in case of spaces)
        while char != "[":
            char = next(chars, None)
        # then consume the element bracket contents
        if char == "[":
            content = _consume_bracket(chars, char)
        char = next(chars, None)
        # split content into args list
        content = content[1:-1] # remove enclosing brackets
        content = _split_except(content)
        # recursively load all subelems
        for i,item in enumerate(content):
            if isinstance(item, str) and "[" in item:
                chars = (char for char in item)
                char = next(chars)
                item = _next_elem(chars, char)
                content[i] = item
        return header, content
    
    def _clean_value(string):
        string = string.strip()
        try: string = float(string)
        except: pass
        return string
    
    def _split_except(string):
        "split the string on every comma, except not while inside quotes or square brackets"
        chars = (char for char in string)
        char = next(chars)
        items = []
        consumed = ""
        while char:
            # dont split on quotes, just consume it
            if char in ("'", '"'):
                consumed += _consume_quote(chars, char, char)
            # dont split inside brackets, just consume it
            elif char == "[":
                consumed += _consume_bracket(chars, char)
            # new splitchar found, add what has been consumed so far as an item, reset, and start consuming until next splitchar
            elif char == ",":
                consumed = _clean_value(consumed)
                items.append(consumed)
                consumed = ""
            # consume normal char
            elif char:
                consumed += char
            # next
            char = next(chars, None)
        # append last item too
        consumed = _clean_value(consumed)
        items.append(consumed)
        return items
    
    # load into nested tuples and arglists
    crstuples = []
    chars = (char for char in string)
    char = next(chars)
    while char:
        header,content = _next_elem(chars, char)
        crstuples.append((header, content))
        char = next(chars, None)

    # autodetect wkttype if not specified
    if not wkttype:
        topheader,topcontent = crstuples[0]
        if topheader == "PROJCS":
            geogcsheader,geogcscontent = topcontent[1]
        elif topheader == "GEOGCS":
            geogcsheader,geogcscontent = topheader,topcontent

        # datum elem should be second under geogcs
        datumheader, datumcontent = geogcscontent[1]
        datumname = datumcontent[0].upper().strip('"')
        
        # esri wkt datums all use "D_" before the datum name
        if datumname.startswith("D_"):
            wkttype = "esri"
        else:
            wkttype = "ogc"

    # parse into actual crs objects
    def _parse_top(header, content):
        "procedure for parsing the toplevel crs element and all its children"
        if header.upper() == "PROJCS":
            
            # find name
            csname = content[0].strip('"')
            
            # find geogcs elem (by running parse again)
            subheader, subcontent = content[1]
            geogcs = _parse_top(subheader, subcontent)
            
            # find projection elem
            for part in content:
                if isinstance(part, tuple):
                    subheader,subcontent = part
                    if subheader == "PROJECTION":
                        break
            projname = subcontent[0].strip('"')
            projclass = projections.find(projname, "%s_wkt" % wkttype, strict)
            if projclass:
                proj = projclass()
            else:
                raise NotImplementedError("Unsupported projection: The specified projection name %r could not be found in the list of supported projections" % projname)
            
            # find params
            params = []
            for part in content:
                if isinstance(part, tuple):
                    subheader,subcontent = part
                    if subheader == "PARAMETER":
                        name, value = subcontent[0].strip('"'), subcontent[1]
                        itemclass = parameters.find(name, "%s_wkt" % wkttype, strict)
                        if itemclass:
                            item = itemclass(value)
                            params.append(item)
                            
            # find unit
            for part in content:
                if isinstance(part, tuple):
                    subheader,subcontent = part
                    if subheader == "UNIT":
                        break
            unitname,value = subcontent[0].strip('"'), subcontent[1]
            unitclass = units.find(unitname, "%s_wkt" % wkttype, strict)
            if unitclass:
                unit = unitclass()
            else:
                unit = units.Unknown()

            unit.unitmultiplier.value = value # override default multiplier
            linunit = unit
            
            # find twin axis maybe
##            if len(content) >= 6:
##                twinax = (parameters.Axis(
##            else:
##                twinax = None
            
            # put it all together
            projcs = containers.ProjCS(csname, geogcs, proj, params, linunit) #, twinax)
            return projcs

        elif header.upper() == "GEOGCS":
            # name
            csname = content[0].strip('"')
            
            # datum
            subheader, subcontent = content[1]
            
            ## datum name
            datumname = subcontent[0].strip('"')
            datumclass = datums.find(datumname, "%s_wkt" % wkttype, strict)
            if datumclass:
                datum = datumclass()
            else:
                datum = datums.Unknown()
                
            ## datum ellipsoid
            subsubheader, subsubcontent = subcontent[1]
            ellipsname = subsubcontent[0].strip('"')
            ellipsclass = ellipsoids.find(ellipsname, "%s_wkt" % wkttype, strict)
            if ellipsclass:
                ellipsoid = ellipsclass()
            else:
                ellipsoid = ellipsoids.Unknown()

            ellipsoid.semimaj_ax = parameters.SemiMajorRadius(subsubcontent[1])
            if subsubcontent[2] == 0:
                # WKT falsely sets inverse flattening to 0 for spheroids
                # but actually it cannot be 0, it is the flattening that is 0
                ellipsoid.flat = parameters.Flattening(subsubcontent[2])
            else:
                ellipsoid.inv_flat = parameters.InverseFlattening(subsubcontent[2])

            ## datum shift
            if wkttype == "ogc":
                for subsubheader,subsubcontent in subcontent[1:]:
                    if subsubheader == "TOWGS84":
                        datumshift = parameters.DatumShift(subsubcontent)
                        break
                else:
                    datumshift = None
            elif wkttype == "esri":
                # not used in esri wkt
                datumshift = None
                
            ## put it all togehter
            datum.ellips = ellipsoid
            datum.datumshift = datumshift
            
            # prime mer
            subheader, subcontent = content[2]
            prime_mer = parameters.PrimeMeridian(subcontent[1])
            
            # angunit
            subheader, subcontent = content[3]
            unitname,value = subcontent[0].strip('"'), subcontent[1]
            unitclass = units.find(unitname, "%s_wkt" % wkttype, strict)
            if unitclass:
                unit = unitclass()
            else:
                unit = units.Unknown()
            unit.unitmultiplier.value = value # override default multiplier
            angunit = unit
            
            # twin axis
            # ...
            
            # put it all together
            geogcs = containers.GeogCS(csname, datum, prime_mer, angunit, twin_ax=None)
            return geogcs

    # toplevel collection
    header, content = crstuples[0]
    crs = _parse_top(header, content)
        
    # use args to create crs
    return crs
def _from_wkt ( string , wkttype = None , strict = False ) : # TODO # - Make function for finding next elemt by name, instead of knowing its arg index position # - Maybe verify elem arg name # make sure valid wkttype if wkttype : wkttype = wkttype . lower ( ) assert wkttype in ( "ogc" , "esri" , None ) # remove newlines and multi spaces string = " " . join ( string . split ( ) ) # parse arguments into components def _consume_bracket ( chars , char ) : "char must be the opening bracket" consumed = "" depth = 1 while char and depth > 0 : consumed += char char = next ( chars , None ) # update depth level if char == "[" : depth += 1 elif char == "]" : depth -= 1 consumed += char # consume the last closing char too return consumed def _consume_quote ( chars , char , quotechar ) : "char and quotechar must be the opening quote char" consumed = "" # consume the first opening char consumed += char char = next ( chars , None ) # consume inside while char and char != quotechar : consumed += char char = next ( chars , None ) # consume the last closing char too consumed += char return consumed def _next_elem ( chars , char ) : "char must be the first char of the text that precedes brackets" header = "" # skip until next header while not char . isalpha ( ) : char = next ( chars , None ) # first consume the element text header while char . isalpha ( ) : header += char char = next ( chars , None ) # skip until next brackets (in case of spaces) while char != "[" : char = next ( chars , None ) # then consume the element bracket contents if char == "[" : content = _consume_bracket ( chars , char ) char = next ( chars , None ) # split content into args list content = content [ 1 : - 1 ] # remove enclosing brackets content = _split_except ( content ) # recursively load all subelems for i , item in enumerate ( content ) : if isinstance ( item , str ) and "[" in item : chars = ( char for char in item ) char = next ( chars ) item = _next_elem ( chars , char ) content [ i ] = item return header , content def _clean_value ( string ) : string = string . strip ( ) try : string = float ( string ) except : pass return string def _split_except ( string ) : "split the string on every comma, except not while inside quotes or square brackets" chars = ( char for char in string ) char = next ( chars ) items = [ ] consumed = "" while char : # dont split on quotes, just consume it if char in ( "'" , '"' ) : consumed += _consume_quote ( chars , char , char ) # dont split inside brackets, just consume it elif char == "[" : consumed += _consume_bracket ( chars , char ) # new splitchar found, add what has been consumed so far as an item, reset, and start consuming until next splitchar elif char == "," : consumed = _clean_value ( consumed ) items . append ( consumed ) consumed = "" # consume normal char elif char : consumed += char # next char = next ( chars , None ) # append last item too consumed = _clean_value ( consumed ) items . append ( consumed ) return items # load into nested tuples and arglists crstuples = [ ] chars = ( char for char in string ) char = next ( chars ) while char : header , content = _next_elem ( chars , char ) crstuples . append ( ( header , content ) ) char = next ( chars , None ) # autodetect wkttype if not specified if not wkttype : topheader , topcontent = crstuples [ 0 ] if topheader == "PROJCS" : geogcsheader , geogcscontent = topcontent [ 1 ] elif topheader == "GEOGCS" : geogcsheader , geogcscontent = topheader , topcontent # datum elem should be second under geogcs datumheader , datumcontent = geogcscontent [ 1 ] datumname = datumcontent [ 0 ] . upper ( ) . strip ( '"' ) # esri wkt datums all use "D_" before the datum name if datumname . startswith ( "D_" ) : wkttype = "esri" else : wkttype = "ogc" # parse into actual crs objects def _parse_top ( header , content ) : "procedure for parsing the toplevel crs element and all its children" if header . upper ( ) == "PROJCS" : # find name csname = content [ 0 ] . strip ( '"' ) # find geogcs elem (by running parse again) subheader , subcontent = content [ 1 ] geogcs = _parse_top ( subheader , subcontent ) # find projection elem for part in content : if isinstance ( part , tuple ) : subheader , subcontent = part if subheader == "PROJECTION" : break projname = subcontent [ 0 ] . strip ( '"' ) projclass = projections . find ( projname , "%s_wkt" % wkttype , strict ) if projclass : proj = projclass ( ) else : raise NotImplementedError ( "Unsupported projection: The specified projection name %r could not be found in the list of supported projections" % projname ) # find params params = [ ] for part in content : if isinstance ( part , tuple ) : subheader , subcontent = part if subheader == "PARAMETER" : name , value = subcontent [ 0 ] . strip ( '"' ) , subcontent [ 1 ] itemclass = parameters . find ( name , "%s_wkt" % wkttype , strict ) if itemclass : item = itemclass ( value ) params . append ( item ) # find unit for part in content : if isinstance ( part , tuple ) : subheader , subcontent = part if subheader == "UNIT" : break unitname , value = subcontent [ 0 ] . strip ( '"' ) , subcontent [ 1 ] unitclass = units . find ( unitname , "%s_wkt" % wkttype , strict ) if unitclass : unit = unitclass ( ) else : unit = units . Unknown ( ) unit . unitmultiplier . value = value # override default multiplier linunit = unit # find twin axis maybe ##            if len(content) >= 6: ##                twinax = (parameters.Axis( ##            else: ##                twinax = None # put it all together projcs = containers . ProjCS ( csname , geogcs , proj , params , linunit ) #, twinax) return projcs elif header . upper ( ) == "GEOGCS" : # name csname = content [ 0 ] . strip ( '"' ) # datum subheader , subcontent = content [ 1 ] ## datum name datumname = subcontent [ 0 ] . strip ( '"' ) datumclass = datums . find ( datumname , "%s_wkt" % wkttype , strict ) if datumclass : datum = datumclass ( ) else : datum = datums . Unknown ( ) ## datum ellipsoid subsubheader , subsubcontent = subcontent [ 1 ] ellipsname = subsubcontent [ 0 ] . strip ( '"' ) ellipsclass = ellipsoids . find ( ellipsname , "%s_wkt" % wkttype , strict ) if ellipsclass : ellipsoid = ellipsclass ( ) else : ellipsoid = ellipsoids . Unknown ( ) ellipsoid . semimaj_ax = parameters . SemiMajorRadius ( subsubcontent [ 1 ] ) if subsubcontent [ 2 ] == 0 : # WKT falsely sets inverse flattening to 0 for spheroids # but actually it cannot be 0, it is the flattening that is 0 ellipsoid . flat = parameters . Flattening ( subsubcontent [ 2 ] ) else : ellipsoid . inv_flat = parameters . InverseFlattening ( subsubcontent [ 2 ] ) ## datum shift if wkttype == "ogc" : for subsubheader , subsubcontent in subcontent [ 1 : ] : if subsubheader == "TOWGS84" : datumshift = parameters . DatumShift ( subsubcontent ) break else : datumshift = None elif wkttype == "esri" : # not used in esri wkt datumshift = None ## put it all togehter datum . ellips = ellipsoid datum . datumshift = datumshift # prime mer subheader , subcontent = content [ 2 ] prime_mer = parameters . PrimeMeridian ( subcontent [ 1 ] ) # angunit subheader , subcontent = content [ 3 ] unitname , value = subcontent [ 0 ] . strip ( '"' ) , subcontent [ 1 ] unitclass = units . find ( unitname , "%s_wkt" % wkttype , strict ) if unitclass : unit = unitclass ( ) else : unit = units . Unknown ( ) unit . unitmultiplier . value = value # override default multiplier angunit = unit # twin axis # ... # put it all together geogcs = containers . GeogCS ( csname , datum , prime_mer , angunit , twin_ax = None ) return geogcs # toplevel collection header , content = crstuples [ 0 ] crs = _parse_top ( header , content ) # use args to create crs return crs
Internal method for parsing wkt, with minor differences depending on ogc or esri style.

    Arguments:

    - *string*: The OGC or ESRI WKT representation as a string.
    - *wkttype* (optional): How to parse the WKT string, as either 'ogc', 'esri', or None. If None, tries to autodetect the wkt type before parsing (default). 
    - *strict* (optional): When True, the parser is strict about names having to match
        exactly with upper and lowercases. Default is not strict (False).

    Returns:

    - A CS instance of the indicated type.
Internal method for parsing wkt with minor differences depending on ogc or esri style .