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
}
|