aboutsummaryrefslogtreecommitdiff
path: root/osmpass/enrich.go
diff options
context:
space:
mode:
Diffstat (limited to 'osmpass/enrich.go')
-rw-r--r--osmpass/enrich.go240
1 files changed, 0 insertions, 240 deletions
diff --git a/osmpass/enrich.go b/osmpass/enrich.go
deleted file mode 100644
index 3af0067..0000000
--- a/osmpass/enrich.go
+++ /dev/null
@@ -1,240 +0,0 @@
-package osmpass
-
-import (
- "database/sql"
- "fmt"
- "log/slog"
- "strings"
- "unicode"
-
- "golang.org/x/text/unicode/norm"
-)
-
-const elevationToleranceM = 25
-
-type osmPass struct {
- Name string
- Elevation *int
- Latitude float64
- Longitude float64
-}
-
-func EnrichMountainPasses(db *sql.DB) (int, error) {
- osmPasses, err := loadOSMPasses(db)
- if err != nil {
- return 0, err
- }
- rows, err := db.Query(`
- SELECT external_id, name, department_code, elevation
- FROM mountain_passes
- WHERE latitude IS NULL
- `)
- if err != nil {
- return 0, err
- }
-
- unmatched := make([]struct {
- ExternalID string
- Name string
- DepartmentCode string
- Elevation int
- }, 0)
- for rows.Next() {
- var candidate struct {
- ExternalID string
- Name string
- DepartmentCode string
- Elevation int
- }
- if err := rows.Scan(&candidate.ExternalID, &candidate.Name, &candidate.DepartmentCode, &candidate.Elevation); err != nil {
- rows.Close()
- return 0, err
- }
- unmatched = append(unmatched, candidate)
- }
- if err := rows.Err(); err != nil {
- rows.Close()
- return 0, err
- }
- rows.Close()
-
- statement, err := db.Prepare(`
- UPDATE mountain_passes SET latitude = ?, longitude = ? WHERE external_id = ?
- `)
- if err != nil {
- return 0, err
- }
- defer statement.Close()
-
- count := 0
- for _, candidate := range unmatched {
- matched := matchToOSM(candidate.Name, candidate.DepartmentCode, candidate.Elevation, osmPasses)
- if matched == nil {
- continue
- }
- if _, err := statement.Exec(matched.Latitude, matched.Longitude, candidate.ExternalID); err != nil {
- return count, err
- }
- count++
- }
- slog.Info("Enriched mountain passes with coordinates", "count", count)
- return count, nil
-}
-
-func loadOSMPasses(db *sql.DB) ([]osmPass, error) {
- rows, err := db.Query(`
- SELECT name, elevation, latitude, longitude
- FROM osm_passes
- WHERE elevation IS NOT NULL
- `)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
-
- osmPasses := make([]osmPass, 0)
- for rows.Next() {
- var mountainPass osmPass
- var name sql.NullString
- var elevation sql.NullInt64
- if err := rows.Scan(&name, &elevation, &mountainPass.Latitude, &mountainPass.Longitude); err != nil {
- return nil, err
- }
- mountainPass.Name = name.String
- if elevation.Valid {
- elevationValue := int(elevation.Int64)
- mountainPass.Elevation = &elevationValue
- }
- osmPasses = append(osmPasses, mountainPass)
- }
- return osmPasses, rows.Err()
-}
-
-func matchToOSM(name, departmentCode string, elevation int, osmPasses []osmPass) *osmPass {
- bbox := departmentBBox(departmentCode)
- var best *osmPass
- bestNameMatch := false
- bestElevationDiff := 0.0
- for i := range osmPasses {
- candidate := &osmPasses[i]
- if candidate.Elevation == nil {
- continue
- }
- if !bbox.Contains(candidate.Latitude, candidate.Longitude) {
- continue
- }
- elevationDiff := absFloat64(float64(elevation) - float64(*candidate.Elevation))
- if elevationDiff > elevationToleranceM {
- continue
- }
- nameMatch := nameMatches(name, candidate.Name)
- if best == nil ||
- (nameMatch && !bestNameMatch) ||
- (nameMatch == bestNameMatch && elevationDiff < bestElevationDiff) {
- best = candidate
- bestNameMatch = nameMatch
- bestElevationDiff = elevationDiff
- }
- }
- if best == nil {
- return nil
- }
- if bestNameMatch {
- return best
- }
- for i := range osmPasses {
- candidate := &osmPasses[i]
- if candidate == best || candidate.Elevation == nil {
- continue
- }
- if !bbox.Contains(candidate.Latitude, candidate.Longitude) {
- continue
- }
- if absFloat64(float64(elevation)-float64(*candidate.Elevation)) <= bestElevationDiff+30 {
- return nil
- }
- }
- return best
-}
-
-type bbox struct {
- minLat, minLon, maxLat, maxLon float64
-}
-
-func (b bbox) Contains(latitude, longitude float64) bool {
- return latitude >= b.minLat && latitude <= b.maxLat &&
- longitude >= b.minLon && longitude <= b.maxLon
-}
-
-func departmentBBox(departmentCode string) bbox {
- switch departmentCode {
- case "01":
- return bbox{minLat: 45.5, minLon: 4.7, maxLat: 46.6, maxLon: 6.3}
- case "06":
- return bbox{minLat: 43.5, minLon: 6.4, maxLat: 44.5, maxLon: 7.8}
- case "13":
- return bbox{minLat: 43.1, minLon: 4.4, maxLat: 44.0, maxLon: 6.0}
- default:
- return bbox{minLat: 41.0, minLon: -6.0, maxLat: 52.0, maxLon: 10.0}
- }
-}
-
-func nameMatches(centName, osmName string) bool {
- cent := normalizeName(centName)
- osm := normalizeName(osmName)
- if cent == osm {
- return true
- }
- centTokens := tokenize(cent)
- if len(centTokens) < 3 {
- return false
- }
- osmTokens := make(map[string]bool)
- for _, token := range tokenize(osm) {
- osmTokens[token] = true
- }
- for _, token := range centTokens {
- if !osmTokens[token] {
- return false
- }
- }
- return true
-}
-
-func tokenize(name string) []string {
- return strings.Fields(name)
-}
-
-func normalizeName(name string) string {
- name = strings.ToLower(name)
- name = norm.NFD.String(name)
- normalized := strings.Builder{}
- previousSpace := false
- for _, r := range name {
- if unicode.Is(unicode.Mn, r) {
- continue
- }
- if unicode.IsLetter(r) || unicode.IsDigit(r) {
- normalized.WriteRune(r)
- previousSpace = false
- } else if !previousSpace {
- normalized.WriteRune(' ')
- previousSpace = true
- }
- }
- return strings.TrimSpace(normalized.String())
-}
-
-func absFloat64(value float64) float64 {
- if value < 0 {
- return -value
- }
- return value
-}
-
-func (p osmPass) String() string {
- if p.Elevation == nil {
- return fmt.Sprintf("%s @ unknown elevation", p.Name)
- }
- return fmt.Sprintf("%s @ %dm", p.Name, *p.Elevation)
-}