From 757f611a4d738688bc97cc6787ecce8650f25873 Mon Sep 17 00:00:00 2001 From: Petri Hienonen Date: Sun, 1 Feb 2026 17:31:30 +0200 Subject: Second --- parser.go | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 parser.go (limited to 'parser.go') diff --git a/parser.go b/parser.go new file mode 100644 index 0000000..6cb67e8 --- /dev/null +++ b/parser.go @@ -0,0 +1,129 @@ +// parser.go - XML parsing and data extraction +package main + +import ( + "encoding/xml" + "fmt" + "math" + "strconv" + "strings" +) + +// ParseForecastXML parses XML response into ForecastData +func ParseForecastXML(xmlData []byte) (*ForecastData, error) { + var fc FeatureCollection + if err := xml.Unmarshal(xmlData, &fc); err != nil { + return nil, fmt.Errorf("unmarshaling XML: %w", err) + } + + if len(fc.Members) == 0 { + return nil, fmt.Errorf("no data found in response") + } + + mpc := fc.Members[0].GridSeriesObservation.Result.MultiPointCoverage + + // Parse positions + lat, lon, times, err := parsePositions(mpc.DomainSet.SimpleMultiPoint.Positions) + if err != nil { + return nil, fmt.Errorf("parsing positions: %w", err) + } + + // Parse parameters + params := make([]string, 0, len(mpc.RangeType.DataRecord.Fields)) + paramIndex := make(map[string]int) + for i, f := range mpc.RangeType.DataRecord.Fields { + params = append(params, f.Name) + paramIndex[f.Name] = i + } + + // Parse values + values, err := parseTupleList(mpc.RangeSet.DataBlock.TupleList, len(times), len(params)) + if err != nil { + return nil, fmt.Errorf("parsing values: %w", err) + } + + return &ForecastData{ + Latitude: lat, + Longitude: lon, + Timezone: "Europe/Helsinki", // Default, could be parsed from XML + Timestamps: times, + Parameters: params, + Values: values, + ParamIndex: paramIndex, + }, nil +} + +func parsePositions(positionsStr string) (lat, lon float64, times []int64, err error) { + lines := strings.Split(strings.TrimSpace(positionsStr), "\n") + + for i, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + + parts := strings.Fields(line) + if len(parts) != 3 { + return 0, 0, nil, fmt.Errorf("invalid position format at line %d: %s", i, line) + } + + parsedLat, err := strconv.ParseFloat(parts[0], 64) + if err != nil { + return 0, 0, nil, fmt.Errorf("parsing latitude: %w", err) + } + + parsedLon, err := strconv.ParseFloat(parts[1], 64) + if err != nil { + return 0, 0, nil, fmt.Errorf("parsing longitude: %w", err) + } + + timestamp, err := strconv.ParseInt(parts[2], 10, 64) + if err != nil { + return 0, 0, nil, fmt.Errorf("parsing timestamp: %w", err) + } + + if i == 0 { + lat, lon = parsedLat, parsedLon + } else if math.Abs(parsedLat-lat) > 0.001 || math.Abs(parsedLon-lon) > 0.001 { + return 0, 0, nil, fmt.Errorf("inconsistent location at line %d", i) + } + + times = append(times, timestamp) + } + + if len(times) == 0 { + return 0, 0, nil, fmt.Errorf("no positions found") + } + + return lat, lon, times, nil +} + +func parseTupleList(tupleStr string, numPoints, numParams int) ([][]float64, error) { + tupleStr = strings.TrimSpace(tupleStr) + valuesStr := strings.Fields(tupleStr) + + if len(valuesStr) != numPoints*numParams { + return nil, fmt.Errorf("data mismatch: expected %d values, got %d", + numPoints*numParams, len(valuesStr)) + } + + values := make([][]float64, numPoints) + for i := 0; i < numPoints; i++ { + row := make([]float64, numParams) + for j := 0; j < numParams; j++ { + valStr := valuesStr[i*numParams+j] + if valStr == "NaN" { + row[j] = math.NaN() + } else { + val, err := strconv.ParseFloat(valStr, 64) + if err != nil { + return nil, fmt.Errorf("parsing value at [%d][%d]: %w", i, j, err) + } + row[j] = val + } + } + values[i] = row + } + + return values, nil +} -- cgit v1.3-1-g0d28