summaryrefslogtreecommitdiff
path: root/osmpass/extract.go
blob: cda68907a277f1c6690dd7266a9ffa49f39f5cff (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package osmpass

import (
	"context"
	"database/sql"
	"fmt"
	"io"
	"log/slog"
	"os"
	"strconv"
	"strings"

	"github.com/paulmach/osm"
	"github.com/paulmach/osm/osmpbf"
)

const insertStatement = `
	INSERT INTO osm_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
}