xmlgounmarshallinggml-geographic-markup-lanxml-encoding

Error unmarshalling GML with golang encoding/xml


I am trying to unmarshal some XML, actually Geography Markup Language (GML).

I have an example at http://play.golang.org/p/qS6GjCOtHF

Two problems, the first:

error reading xml main.FeatureCollection field "LowerCorner" with tag "boundedBy>Envelope>lowerCorner" conflicts with field "Envelope" with tag "boundedBy>Envelope"

I have no idea how to fix that. I commented those out and get the GML to unmarshal without errors but then there are no Features in the FeatureCollection.

Any clues?

An example of the GML is:

<?xml version="1.0" encoding="UTF-8"?>
<gml:FeatureCollection xmlns:gml="http://www.opengis.net/gml"
    xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:fme="http://www.safe.com/gml/fme" xsi:schemaLocation="http://www.safe.com/gml/fme tblMainGML.xsd">
    <gml:boundedBy>
        <gml:Envelope srsName="EPSG:3112" srsDimension="2">
            <gml:lowerCorner>45.2921142578125 -80.2166748046875</gml:lowerCorner>
            <gml:upperCorner>169.000122070313 -9.14251708984375</gml:upperCorner>
        </gml:Envelope>
    </gml:boundedBy>
    <gml:featureMember>
        <fme:GML gml:id="id5255fa48-42b3-43d1-9e0d-b2ba8b57a936">
            <fme:OBJECTID>1</fme:OBJECTID>
            <fme:RECORD_ID>QLD48234</fme:RECORD_ID>
            <fme:NAME>HATCHMAN POINT</fme:NAME>
            <fme:FEAT_CODE>PT</fme:FEAT_CODE>
            <fme:CGDN>N</fme:CGDN>
            <fme:AUTHORITY_ID>QLD</fme:AUTHORITY_ID>
            <fme:CONCISE_GAZ>N</fme:CONCISE_GAZ>
            <fme:LATITUDE>-12.58361</fme:LATITUDE>
            <fme:lat_degrees>-12</fme:lat_degrees>
            <fme:lat_minutes>35</fme:lat_minutes>
            <fme:lat_seconds>0</fme:lat_seconds>
            <fme:LONGITUDE>141.62583</fme:LONGITUDE>
            <fme:long_degrees>141</fme:long_degrees>
            <fme:long_minutes>37</fme:long_minutes>
            <fme:long_seconds>32</fme:long_seconds>
            <fme:STATE_ID>QLD</fme:STATE_ID>
            <fme:STATUS>U</fme:STATUS>
            <fme:VARIANT_NAME />
            <fme:MAP_100K>7272</fme:MAP_100K>
            <fme:Place_ID>45880</fme:Place_ID>
            <gml:pointProperty>
                <gml:Point srsName="EPSG:3112" srsDimension="2">
                    <gml:pos>141.625915527344 -12.5836181640625</gml:pos>
                </gml:Point>
            </gml:pointProperty>
        </fme:GML>
    </gml:featureMember>
</gml:FeatureCollection>
</xml>

My structs

type FeatureCollection struct {
    Xsi            string   `xml:"xsi,attr"`
    Fme            string   `xml:"fme,attr"`
    Gml            string   `xml:"gml,attr"`
    Xlink          string   `xml:"xlink,attr"`
    LowerCorner    string   `xml:"boundedBy>Envelope>lowerCorner"`
    UpperCorner    string   `xml:"boundedBy>Envelope>upperCorner"`
    Envelope       Envelope `xml:"boundedBy>Envelope"`
    SchemaLocation string   `xml:"schemaLocation,attr"`
    Features       []Feature
}

type Feature struct {
    PlaceID     string `xml:"featureMember>GML>Place_ID"`
    StateID     string `xml:"featureMember>GML>STATE_ID"`
    Postcode    string `xml:"featureMember>GML>POSTCODE"`
    CGDN        string `xml:"featureMember>GML>CGDN"`
    Map100K     string `xml:"featureMember>GML>MAP_100K"`
ETC...
}

Solution

  • An XML tag can only be mapped to (at most) one struct field. The encoding/xml package has to decide for each XML tag into which struct field it will be decoded. Your structs that model the XML are weird, and make this decision ambiguous.

    For example let's take this example:

    type FeatureCollection struct {
        ...
        LowerCorner    string   `xml:"boundedBy>Envelope>lowerCorner"`
        UpperCorner    string   `xml:"boundedBy>Envelope>upperCorner"`
        Envelope       Envelope `xml:"boundedBy>Envelope"`
        ...
    }
    

    The encoding/xml package can't decide where the XML tag <Envelope> should be decoded into, e.g. into LowerCorner? into UpperCorner? into Envelope? Yes, I know LowerCorner is only a sub-element of <Envelope> but since the whole <Envelope> element is mapped to FeatureCollection.Envelope, this is not allowed.

    You should move LowerCorner and UpperCorner fields into your Envelope struct type because that's where they belong to and you want to unmarshal the whole Envelope xml tag (or if not, FeatureCollection.Envelope could be removed entirely). So follow this pattern to put fields to where they belong.

    Here is your updated model which extracts all the info you wanted:

    type FeatureCollection struct {
        Xsi            string   `xml:"xsi,attr"`
        Fme            string   `xml:"fme,attr"`
        Gml            string   `xml:"gml,attr"`
        Xlink          string   `xml:"xlink,attr"`
        Envelope       Envelope `xml:"boundedBy>Envelope"`
        SchemaLocation string   `xml:"schemaLocation,attr"`
        FeaturesGML    []GML    `xml:"featureMember>GML"`
    }
    type Envelope struct {
        SrsName      string `xml:"srsName,attr"`
        SrsDimension string `xml:"srsDimension,attr"`
        LowerCorner  string `xml:"lowerCorner"`
        UpperCorner  string `xml:"upperCorner"`
    }
    type GML struct {
        ID          string `xml:"id,attr"`
        PlaceID     string `xml:"Place_ID"`
        StateID     string `xml:"STATE_ID"`
        Postcode    string `xml:"POSTCODE"`
        CGDN        string `xml:"CGDN"`
        Map100K     string `xml:"MAP_100K"`
        Point       Point  `xml:"pointProperty>Point"`
        VariantName string `xml:"VARIANT_NAME"`
        RecordID    string `xml:"RECORD_ID"`
        LatSeconds  string `xml:"lat_seconds"`
        Status      string `xml:"STATUS"`
        LongSeconds string `xml:"long_seconds"`
        ConciseGAZ  string `xml:"CONCISE_GAZ"`
        Lattitude   string `xml:"LATITUDE"`
        AuthorityID string `xml:"AUTHORITY_ID"`
        Longitude   string `xml:"LONGITUDE"`
        LongMinutes string `xml:"long_minutes"`
        LatDegrees  string `xml:"lat_degrees"`
        NAME        string `xml:"NAME"`
        LatMinutes  string `xml:"lat_minutes"`
        ObjectID    string `xml:"OBJECTID"`
        FeatCode    string `xml:"FEAT_CODE"`
        LongDegrees string `xml:"long_degrees"`
    }
    type Point struct {
        SrsName      string `xml:"srsName,attr"`
        SrsDimension string `xml:"srsDimension,attr"`
        Pos          string `xml:"pos"`
    }
    

    Here is the modified version of your code on the Go Playground which runs without errors.

    To verify that your struct holds all the unmarshalled info from the XML:

    fmt.Printf("%+v", v)
    

    Output:

    &{Xsi:http://www.w3.org/2001/XMLSchema-instance Fme:http://www.safe.com/gml/fme Gml:http://www.opengis.net/gml Xlink:http://www.w3.org/1999/xlink Envelope:{SrsName:EPSG:3112 SrsDimension:2 LowerCorner:45.2921142578125 -80.2166748046875 UpperCorner:169.000122070313 -9.14251708984375} SchemaLocation:http://www.safe.com/gml/fme tblMainGML.xsd FeaturesGML:[{ID:id5255fa48-42b3-43d1-9e0d-b2ba8b57a936 PlaceID:45880 StateID:QLD Postcode: CGDN:N Map100K:7272 Point:{SrsName:EPSG:3112 SrsDimension:2 Pos:141.625915527344 -12.5836181640625} VariantName: RecordID:QLD48234 LatSeconds:0 Status:U LongSeconds:32 ConciseGAZ:N Lattitude:-12.58361 AuthorityID:QLD Longitude:141.62583 LongMinutes:37 LatDegrees:-12 NAME:HATCHMAN POINT LatMinutes:35 ObjectID:1 FeatCode:PT LongDegrees:141}]}