diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-21 10:30:27 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-21 10:30:27 +0200 |
| commit | 44f7333d9deef81d3468e20b378c63692a9406ec (patch) | |
| tree | aeacb99247dba4e48ee61e2792f5476a3770795a /mountain_pass/extract.go | |
| parent | 6667ee479c06c14bb182f36a0a9ee704fca6218d (diff) | |
Use OSM as the single source of mountain passes
Replace the centcols CSV download/enrichment pipeline with OSM PBF
extraction as the primary (only) source: merge osmpass into mountain_pass,
drop the centcols table and model fields, filter to named passes with
elevation, and remove the centcols CLI flags.
Diffstat (limited to 'mountain_pass/extract.go')
| -rw-r--r-- | mountain_pass/extract.go | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/mountain_pass/extract.go b/mountain_pass/extract.go new file mode 100644 index 0000000..81bed68 --- /dev/null +++ b/mountain_pass/extract.go @@ -0,0 +1,98 @@ +package mountain_pass + +import ( + "context" + "database/sql" + "fmt" + "io" + "log/slog" + "os" + "strconv" + "strings" + + "github.com/paulmach/osm" + "github.com/paulmach/osm/osmpbf" +) + +const insertStatement = ` + INSERT INTO mountain_passes (osm_id, name, elevation, latitude, longitude) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT(osm_id) DO UPDATE SET + name = excluded.name, + elevation = excluded.elevation, + latitude = excluded.latitude, + longitude = excluded.longitude +` + +func ExtractMountainPasses(ctx context.Context, pbfPath string, db *sql.DB) (int, error) { + file, err := os.Open(pbfPath) + if err != nil { + return 0, fmt.Errorf("failed to open pbf file: %w", err) + } + defer file.Close() + + scanner := osmpbf.New(ctx, file, 4) + scanner.SkipWays = true + scanner.SkipRelations = true + scanner.FilterNode = isMountainPass + + tx, err := db.BeginTx(ctx, nil) + if err != nil { + return 0, fmt.Errorf("failed to begin transaction: %w", err) + } + defer tx.Rollback() + + stmt, err := tx.PrepareContext(ctx, insertStatement) + if err != nil { + return 0, fmt.Errorf("failed to prepare insert: %w", err) + } + defer stmt.Close() + + count := 0 + for scanner.Scan() { + node, ok := scanner.Object().(*osm.Node) + if !ok { + continue + } + elevation, hasElevation := parseElevation(node.Tags.Find("ele")) + var elevationValue any + if hasElevation { + elevationValue = elevation + } + if _, err := stmt.ExecContext(ctx, node.ID, node.Tags.Find("name"), elevationValue, node.Lat, node.Lon); err != nil { + return count, fmt.Errorf("failed to insert osm pass %d: %w", node.ID, err) + } + count++ + } + if err := scanner.Err(); err != nil && err != io.EOF { + return count, fmt.Errorf("failed to scan pbf: %w", err) + } + if err := tx.Commit(); err != nil { + return count, fmt.Errorf("failed to commit transaction: %w", err) + } + slog.Info("Extracted mountain passes", "count", count, "source", pbfPath) + return count, nil +} + +func isMountainPass(node *osm.Node) bool { + if node.Tags.Find("mountain_pass") == "yes" { + return true + } + return node.Tags.Find("natural") == "mountain_pass" +} + +func parseElevation(ele string) (int, bool) { + ele = strings.TrimSpace(ele) + if ele == "" { + return 0, false + } + for _, candidate := range strings.Fields(ele) { + if value, err := strconv.Atoi(candidate); err == nil { + return value, true + } + if value, err := strconv.ParseFloat(candidate, 64); err == nil { + return int(value), true + } + } + return 0, false +} |
